[Python] Persistence issue in Flask: basic fields are lost and data is duplicated when...

Discussão em 'Python' iniciado por Stack, Abril 2, 2025.

  1. Stack

    Stack Membro Participativo

    ** I am developing a Flask application that generates meeting minutes. When a user modifies an individual field (such as "customer"), other basic fields (such as "date", "author", etc.) are lost, and some data such as "topics" and "risks" are duplicated. The context does not properly maintain data between modifications.

    Specific Problem:

    When you modify a field (e.g., customer:gsbat), other basic fields are lost

    Arrays as "topics" and "risks" are duplicated

    The deepcopy does not seem to be properly preserving the previous state **

    def create_empty_context():
    return {
    'cliente': None,
    'fecha': None,
    'hora': None,
    'duracion': None,
    'autor': None,
    'nombre': None,
    'asistentes': [],
    'orden_dia': [],
    'temas': [],
    'riesgos': [],
    'comentarios': [],
    'tareas': [],
    'pasos': []
    }

    context = create_empty_context()

    def is_complete_summary(text):
    required_fields = ['cliente', 'fecha', 'duracion', 'autor', 'nombre']
    text_lower = text.lower()

    if any(f"{field}:" in text_lower for field in required_fields):
    return True

    patterns = [
    r'(cliente|empresa|contratante)[:\s]',
    r'(fecha|día|reunión el|reunión\s*\d+)',
    r'(duracion|duró|tiempo)[:\s]',
    r'(autor|escrita\s*por|responsable)[:\s]',
    r'(proyecto|nombre\s*del\s*proyecto)[:\s]'
    ]
    return sum(1 for pattern in patterns if re.search(pattern, text_lower)) >= 3

    def extract_field_from_text(field, text):
    patterns = {
    'cliente': r'(cliente|empresa|contratante)[:\s]([^\n,;]+)',
    'fecha': r'(fecha|día|reunión el|el\s\d+)[:\s]([^\n,;]+)',
    'hora': r'(hora|comenzó a las|a las)[:\s]([^\n,;]+)',
    'duracion': r'(duracion|duró|tiempo)[:\s]([^\n,;]+)',
    'autor': r'(autor|escrita por|responsable)[:\s]([^\n,;]+)',
    'nombre': r'(proyecto|nombre del proyecto)[:\s]*([^\n,;]+)'
    }

    if field in patterns:
    match = re.search(patterns[field], text, re.IGNORECASE)
    return match.group(2).strip() if match else None
    return None

    def get_missing_fields():
    required_fields = ['cliente', 'fecha', 'duracion', 'autor', 'nombre']
    return [field for field in required_fields if not context.get(field)]

    def handle_field_update(field, value, is_add_mode=False):
    global context

    if field in ['cliente', 'fecha', 'hora', 'duracion', 'autor', 'nombre']:
    context[field] = value
    return

    if field in ['orden_dia', 'comentarios', 'pasos']:
    if isinstance(value, str):
    items = [item.strip() for item in value.split(',') if item.strip()]
    if is_add_mode:
    context[field].extend(items)
    else:
    context[field] = items
    return

    if field == 'asistentes':
    if isinstance(value, str):
    new_names = [name.strip() for name in value.split(',') if name.strip()]
    if is_add_mode:
    for name in new_names:
    context['asistentes'].append({
    'nombre': name,
    'organizacion': 'No especificada',
    'estado': 'Presente'
    })
    else:
    context['asistentes'] = [{
    'nombre': name,
    'organizacion': 'No especificada',
    'estado': 'Presente'
    } for name in new_names]
    return

    if field == 'temas':
    if isinstance(value, str):
    new_titles = [title.strip() for title in value.split(',') if title.strip()]
    if is_add_mode:
    for title in new_titles:
    context['temas'].append({
    'titulo': title,
    'puntos': [],
    'decisiones': []
    })
    else:
    context['temas'] = [{
    'titulo': title,
    'puntos': [],
    'decisiones': []
    } for title in new_titles]
    return

    if field == 'riesgos':
    if isinstance(value, str):
    new_titles = [title.strip() for title in value.split(',') if title.strip()]
    if is_add_mode:
    for title in new_titles:
    context['riesgos'].append({
    'titulo': title,
    'problema': '',
    'asociado': '',
    'mitigaciones': []
    })
    else:
    context['riesgos'] = [{
    'titulo': title,
    'problema': '',
    'asociado': '',
    'mitigaciones': []
    } for title in new_titles]
    return

    if field == 'tareas':
    if isinstance(value, str):
    new_tasks = [task.strip() for task in value.split(',') if task.strip()]
    if is_add_mode:
    for task in new_tasks:
    context['tareas'].append({
    'tarea': task,
    'responsable': '',
    'fecha': '',
    'estado': ''
    })
    else:
    context['tareas'] = [{
    'tarea': task,
    'responsable': '',
    'fecha': '',
    'estado': ''
    } for task in new_tasks]
    return

    def ensure_default_values(context):
    """Asegura valores por defecto solo para campos vacíos"""
    if not context.get('temas'):
    context['temas'] = [{
    'titulo': 'Temas no especificados',
    'puntos': ['No se registraron puntos clave'],
    'decisiones': ['No se registraron decisiones']
    }]

    if not context.get('riesgos'):
    context['riesgos'] = [{
    'titulo': 'No se identificaron riesgos',
    'problema': 'No aplica',
    'asociado': 'No aplica',
    'mitigaciones': ['No aplica']
    }]

    if not context.get('tareas'):
    context['tareas'] = [{
    'tarea': 'No se registraron tareas',
    'responsable': 'No aplica',
    'fecha': 'No aplica',
    'estado': 'N/A'
    }]

    if not context.get('asistentes'):
    context['asistentes'] = [{
    'nombre': 'No se especificaron asistentes',
    'organizacion': 'No especificada',
    'estado': 'No especificado'
    }]

    if not context.get('orden_dia'):
    context['orden_dia'] = ['No se especificó el orden del día']

    if not context.get('comentarios'):
    context['comentarios'] = ['No hay comentarios registrados']

    if not context.get('pasos'):
    context['pasos'] = ['No se definieron próximos pasos']

    return context

    def generate_draft_response(with_options=False):
    try:
    template_path = 'plantilla_acta2.docx'
    safe_cliente = re.sub(r'[^\w\s-]', '', context.get('cliente', '
    acta')).strip().replace(' ', '')[:30]
    safe_fecha = re.sub(r'[^\w\s-]', '', context.get('fecha', '')).strip().replace(' ',
    '')[:10]
    filename = f"acta_{safe_cliente}_{safe_fecha}.docx"
    output_path = f'output/{filename}'

    os.makedirs('output', exist_ok=True)

    final_context = copy.deepcopy(context)
    final_context = ensure_default_values(final_context)

    if render_docx_template(template_path, final_context, output_path):
    preview_html = render_template('preview.html', context=final_context)

    if with_options:
    return (
    f" Borrador del acta generado:\n"
    f"<div class='preview-container'>{preview_html}</div>\n"
    f"<div class='options'>"
    f"<h3>Opciones disponibles:</h3>"
    f"<div class='option-item'>1. <a href='/download/{filename}'
    class='download-link' id='downloadLink'>Descargar acta</a></div>"
    f"<div class='option-item'>2. <button onclick=\"sendAction('enviar por
    correo')\" class='action-btn'>Enviar por correo</button></div>"
    f"<div class='option-item'>3. <button
    onclick=\"sendAction('modificar')\" class='action-btn'>Modificar
    campos</button></div>"
    f"<div class='option-item'>4. <button onclick=\"sendAction('cerrar')\"
    class='action-btn'>Cerrar</button></div>"
    f"</div>"
    f"<script>"
    f"function sendAction(action) {{"
    f" fetch('/send_message', {{"
    f" method: 'POST',"
    f" headers: {{'Content-Type': 'application/json'}},"
    f" body: JSON.stringify({{message: action}})"
    f" }}).then(response => response.json())"
    f" .then(data => {{"
    f" if(data.action === 'reload') location.reload();"
    f" else alert(data.response || 'Acción completada');"
    f" }});"
    f"}}"
    f"document.getElementById('downloadLink').addEventListener('click',
    function() {{"
    f" fetch('/prepare_download/{filename}')"
    f" .then(response => response.json())"
    f" .then(data => {{"
    f" if(data.redirect) window.location.href = data.redirect;"
    f" }});"
    f"}});"
    f"</script>"
    )
    return preview_html
    return "❌ Error al generar el documento"
    except Exception as e:
    return f"❌ Error al generar el documento: {str(e)}"

    @app.route('/', methods=['GET', 'POST'])
    def chat():
    return render_template('chat.html')

    @app.route('/send_message', methods=['POST'])
    def send_message():
    global context
    data = request.json
    user_message = data.get('message', '').strip()
    user_message_lower = user_message.lower()

    if user_message_lower == 'cerrar':
    context = create_empty_context()
    return jsonify({
    'response': "✅ El proceso ha sido cerrado. Puedes comenzar de nuevo enviando
    otro resumen.",
    'context': context
    })

    if user_message_lower in ['modificar', 'modificar campos'] and not
    data.get('modifying'):
    return jsonify({
    'response': ( "✏️ Modo edición activado. Elige una opción:\n"
    "1. Para MODIFICAR un campo existente (sobrescribe):\n"
    "'campo: valor'\n"
    "2. Para AÑADIR información (mantiene lo existente):\n"
    "'++campo: valor'\n"
    "Ejemplos:\n"
    f"'autor: Francisco López' (modifica el autor)\n"
    f"'asistentes: Carlos Ruiz' (sobrescribe asistentes)\n"
    f"'++asistentes: Carlos Ruiz' (añade asistente)\n"
    "Campos disponibles:\n"
    f"{', '.join(context.keys())}\n"
    "Escribe 'listo' para finalizar"
    ),
    'context': context,
    'modifying': True
    })

    if data.get('modifying'):
    if user_message_lower == 'listo':
    missing_fields = get_missing_fields()
    if missing_fields:
    return jsonify({
    'response': (
    f"⚠️ Faltan campos obligatorios: {', '.join(missing_fields)}\n"
    "¿Deseas completarlos ahora? (responde 'si' para continuar editando
    o 'no' para generar el acta igualmente)"
    ),
    'context': context,
    'confirming_missing': True
    })
    else:
    return jsonify({
    'response': generate_draft_response(with_options=True),
    'context': context,
    'show_options': True
    })

    elif data.get('confirming_missing'):
    if user_message_lower in ['si', 'sí']:
    return jsonify({
    'response': "Continúa modificando los campos faltantes usando 'campo:
    valor' o '++campo: valor' para añadir",
    'context': context,
    'modifying': True
    })
    elif user_message_lower == 'no':
    return jsonify({
    'response': generate_draft_response(with_options=True),
    'context': context,
    'show_options': True
    })

    elif ':' in user_message:
    field_part, value_part = user_message.split(':', 1)
    is_add_mode = field_part.startswith('++')
    field = field_part.replace('++', '').strip().lower()
    value = value_part.strip()

    field_aliases = {
    'proyecto': 'nombre',
    'dia': 'fecha',
    'tiempo': 'duracion',
    'responsable': 'autor'
    }
    field = field_aliases.get(field, field)

    if field in context:
    try:
    current_context = copy.deepcopy(context)

    if field in ['cliente', 'fecha', 'hora', 'duracion', 'autor',
    'nombre']:
    context[field] = value
    else:
    handle_field_update(field, value, is_add_mode)

    for basic_field in ['cliente', 'fecha', 'hora', 'duracion', 'autor',
    'nombre']:
    if basic_field != field and current_context.get(basic_field):
    context[basic_field] = current_context[basic_field]

    return jsonify({
    'response': f"✅ '{field}' actualizado. Continúa modificando o
    escribe 'listo'",
    'context': context,
    'modifying': True
    })
    except Exception as e:
    print(f"Error al procesar modificación: {str(e)}")
    return jsonify({
    'response': f"❌ Error al procesar '{field}'. Inténtalo de nuevo.",
    'context': context,
    'modifying': True
    })
    else:
    return jsonify({
    'response': f"❌ Campo '{field}' no válido. Campos disponibles:\n{', '.join(context.keys())}",
    'context': context,
    'modifying': True
    })

    if is_complete_summary(user_message):
    try:
    parsed_data = parse_meeting_summary(user_message)

    basic_fields = ['cliente', 'fecha', 'hora', 'duracion', 'autor', 'nombre']
    for field in basic_fields:
    if parsed_data.get(field):
    context[field] = parsed_data[field]

    for key, value in parsed_data.items():
    if key not in basic_fields and value and (not isinstance(value, list) or
    len(value) > 0):
    if isinstance(context[key], list) and isinstance(value, list):
    context[key].extend(value)
    else:
    context[key] = value

    missing_fields = get_missing_fields()
    if missing_fields:
    return jsonify({
    'response': (
    f"✅ Información recibida. Faltan campos obligatorios:\n"
    f"{', '.join(missing_fields)}\n"
    f"Por favor completa estos campos o escribe 'generar' para continuar."
    ),
    'context': context,
    'awaiting_fields': True
    })

    return jsonify({
    'response': generate_draft_response(with_options=True),
    'context': context,
    'show_options': True
    })

    except Exception as e:
    print(f"Error en el servidor: {str(e)}")
    return jsonify({
    'response': "❌ Error al procesar el acta. Por favor, verifica el formato e
    intenta nuevamente.",
    'context': context
    })

    Continue reading...

Compartilhe esta Página