jcalbornoz commited on
Commit
6764e62
verified
1 Parent(s): 2556fce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -40
app.py CHANGED
@@ -8,7 +8,6 @@ import pytesseract
8
  import io
9
 
10
  # 1. Configuraci贸n de Flask
11
- # Asumimos que index.html est谩 en la ra铆z del proyecto (junto a app.py).
12
  app = Flask(__name__, template_folder='.')
13
 
14
  # 2. Configuraci贸n de OpenAI
@@ -29,6 +28,7 @@ def ocr_page(img_bytes):
29
  def extract_text_from_file(file):
30
  """
31
  Extrae texto de todas las p谩ginas de un PDF, con fallback a Tesseract OCR para escaneados.
 
32
  """
33
  file_bytes = file.read()
34
  total_text = ""
@@ -37,22 +37,16 @@ def extract_text_from_file(file):
37
  try:
38
  if file.filename.endswith('.pdf'):
39
  pdf_reader = PyPDF2.PdfReader(io.BytesIO(file_bytes))
40
-
41
- # ITERAMOS SOBRE TODAS LAS P脕GINAS
42
  for page in pdf_reader.pages:
43
  total_text += page.extract_text() or ""
44
 
45
- # Si se extrajo una cantidad significativa de texto, 煤salo directamente
46
  if len(total_text.strip()) > 100:
47
- print("Extracci贸n: 脡xito nativo.")
48
  return total_text.strip()
49
 
50
  elif file.filename.endswith('.txt'):
51
- print("Extracci贸n: 脡xito TXT.")
52
  return file_bytes.decode('utf-8').strip()
53
 
54
- except Exception as e:
55
- print(f"Fallo PyPDF2: {e}. Intentando OCR...")
56
  pass
57
 
58
  # --- 2. Fallback a OCR con Tesseract (Solo para PDFs) ---
@@ -61,7 +55,6 @@ def extract_text_from_file(file):
61
  document = fitz.open(stream=file_bytes, filetype="pdf")
62
  ocr_text = ""
63
 
64
- # ITERAMOS SOBRE TODAS LAS P脕GINAS DEL DOCUMENTO
65
  for i in range(len(document)):
66
  page = document.load_page(i)
67
  pix = page.get_pixmap(dpi=300)
@@ -71,55 +64,66 @@ def extract_text_from_file(file):
71
  ocr_text += ocr_page(img_bytes) + "\n"
72
 
73
  if len(ocr_text.strip()) > 100:
74
- print("Extracci贸n: 脡xito OCR.")
75
  return ocr_text.strip()
76
 
77
  except Exception as e:
78
- print(f"Fallo el proceso OCR con Tesseract: {e}")
79
  raise Exception("Fallo la extracci贸n de texto del PDF. Aseg煤rate de que el documento no sea un archivo de imagen corrupto.")
80
 
81
- return ""
82
 
83
 
84
  def generate_summary_openai(text):
85
  """
86
- Genera un an谩lisis experto de 7 puntos clave (m谩s detallado) para CTL/Matr铆cula Inmobiliaria.
87
  """
88
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  prompt_text = (
90
- "Eres un **abogado experto en derecho inmobiliario colombiano** y en el an谩lisis exhaustivo de Certificados de Tradici贸n y Libertad (CTL) o Matr铆culas Inmobiliarias. "
91
- "Tu tarea es analizar detalladamente la historia registral de TODAS las p谩ginas del documento proporcionado y responder con un resumen de **7 puntos clave** utilizando vi帽etas. "
92
- "Los 7 puntos deben ser cr铆ticos y exhaustivos para un estudio de t铆tulos detallado:\n\n"
93
-
94
- "1. **Identificaci贸n del Predio y Propietario Actual**: Menciona el n煤mero de la **Matr铆cula Inmobiliaria**, el nombre del o los propietarios actuales, y el tipo de tenencia (ej. Plena Propiedad, Fideicomiso).\n"
95
- "2. **Grav谩menes Vigentes y Montos**: Indicar **claramente** la existencia o inexistencia de **Hipoteca, Embargo, o Demanda Civil (Litis)**. Si existen, menciona la anotaci贸n y el valor del gravamen si est谩 registrado.\n"
96
- "3. **Limitaciones de Dominio Vigentes**: Indicar la existencia o inexistencia de **Patrimonio de Familia, Afectaci贸n a Vivienda Familiar, o Servidumbres**. Si existe, mencionar el n煤mero de anotaci贸n.\n"
97
- "4. **脷ltima Transacci贸n Registrada**: Detalla el tipo de acto (ej. compraventa, sucesi贸n, liquidaci贸n) y el n煤mero de **Escritura P煤blica** con que se adquiri贸 el inmueble.\n"
98
- "5. **Cancelaci贸n de Grav谩menes Anteriores**: Confirma si todas las hipotecas o embargos anteriores fueron **debidamente cancelados** y menciona el n煤mero de anotaci贸n de la cancelaci贸n.\n"
99
- "6. **Falsa Tradici贸n/Estatuto de Saneamiento**: Indica si existen anotaciones que sugieran Falsa Tradici贸n, o si el inmueble ha sido objeto de procesos de extinci贸n de dominio o saneamiento.\n"
100
- "7. **Conclusi贸n de Titulabilidad**: Breve conclusi贸n legal sobre la **limpieza** del folio, si existen riesgos mayores para un nuevo comprador o entidad financiera, y si el inmueble es apto para una transacci贸n inmediata (Compraventa o Hipoteca).\n\n"
101
-
102
- f"Texto completo del CTL/Matr铆cula Inmobiliaria:\n\n{text}"
103
  )
104
 
105
  response = client.chat.completions.create(
106
- model="gpt-3.5-turbo",
107
  messages=[
108
  {"role": "system", "content": prompt_text}
109
  ],
110
- max_tokens=700, # Aumentado el l铆mite para la respuesta de 7 puntos
111
- n=1,
112
- temperature=0.3, # Baja temperatura para objetividad legal
113
  )
114
 
115
- summary_raw = response.choices[0].message.content.strip()
116
- summary_points = [line.strip() for line in summary_raw.split('\n') if line.strip()]
117
- return summary_points
 
 
 
 
 
 
118
 
119
  except Exception as e:
120
- print(f"Error al llamar a la API de OpenAI: {e}")
121
- # Capturamos errores de la API (ej. clave inv谩lida, l铆mite de tokens)
122
- raise Exception("Error al generar el resumen. Por favor, verifica tu clave de API o que el texto no sea demasiado largo.")
123
 
124
  # --- Rutas de Flask ---
125
 
@@ -140,14 +144,18 @@ def summarize():
140
  raw_text = extract_text_from_file(file)
141
 
142
  if not raw_text:
143
- return jsonify({'error': 'No se pudo extraer texto del archivo. Puede ser un PDF escaneado con muy baja calidad o un formato no soportado.'}), 400
144
 
145
- summary = generate_summary_openai(raw_text)
146
- return jsonify({'summary': summary})
 
 
 
 
 
147
 
148
  except Exception as e:
149
  print(f"Error general en la ruta /summarize: {e}")
150
- # El retorno sigue siendo un Error 500 para el servidor
151
  return jsonify({'error': f"Error interno del servidor: {str(e)}"}), 500
152
 
153
  if __name__ == '__main__':
 
8
  import io
9
 
10
  # 1. Configuraci贸n de Flask
 
11
  app = Flask(__name__, template_folder='.')
12
 
13
  # 2. Configuraci贸n de OpenAI
 
28
  def extract_text_from_file(file):
29
  """
30
  Extrae texto de todas las p谩ginas de un PDF, con fallback a Tesseract OCR para escaneados.
31
+ (La l贸gica de extracci贸n y OCR permanece igual)
32
  """
33
  file_bytes = file.read()
34
  total_text = ""
 
37
  try:
38
  if file.filename.endswith('.pdf'):
39
  pdf_reader = PyPDF2.PdfReader(io.BytesIO(file_bytes))
 
 
40
  for page in pdf_reader.pages:
41
  total_text += page.extract_text() or ""
42
 
 
43
  if len(total_text.strip()) > 100:
 
44
  return total_text.strip()
45
 
46
  elif file.filename.endswith('.txt'):
 
47
  return file_bytes.decode('utf-8').strip()
48
 
49
+ except Exception:
 
50
  pass
51
 
52
  # --- 2. Fallback a OCR con Tesseract (Solo para PDFs) ---
 
55
  document = fitz.open(stream=file_bytes, filetype="pdf")
56
  ocr_text = ""
57
 
 
58
  for i in range(len(document)):
59
  page = document.load_page(i)
60
  pix = page.get_pixmap(dpi=300)
 
64
  ocr_text += ocr_page(img_bytes) + "\n"
65
 
66
  if len(ocr_text.strip()) > 100:
 
67
  return ocr_text.strip()
68
 
69
  except Exception as e:
 
70
  raise Exception("Fallo la extracci贸n de texto del PDF. Aseg煤rate de que el documento no sea un archivo de imagen corrupto.")
71
 
72
+ return ""
73
 
74
 
75
  def generate_summary_openai(text):
76
  """
77
+ Genera un an谩lisis experto y devuelve los datos estructurados en formato JSON.
78
  """
79
  try:
80
+ # Definici贸n del esquema JSON para una salida estructurada
81
+ json_schema = {
82
+ "type": "object",
83
+ "properties": {
84
+ "tipo_documento_detectado": {"type": "string", "description": "Si es CTL/Matr铆cula Inmobiliaria, o Certificado de Junta de Acci贸n Comunal."},
85
+ "identificacion_principal": {"type": "string", "description": "N煤mero de Matr铆cula Inmobiliaria (si es CTL) o Nombre del titular de la propiedad/derecho (si es JAC)."},
86
+ "propietario_actual": {"type": "string", "description": "Nombre completo del propietario o titular del derecho seg煤n el documento."},
87
+ "estado_gravamenes_vigentes": {"type": "string", "description": "Existencia o inexistencia de Hipoteca, Embargo, o Servidumbre. Breve descripci贸n."},
88
+ "ultima_transaccion": {"type": "string", "description": "Acto de la 煤ltima adquisici贸n (ej. Compraventa, Sucesi贸n, o Acto comunal)."},
89
+ "limites_o_restricciones": {"type": "string", "description": "Presencia de Patrimonio de Familia, Afectaci贸n a Vivienda, o restricciones comunales/vecinales."},
90
+ "analisis_riesgo_legal": {"type": "string", "description": "Riesgos legales detectados (ej. litigios, falsas tradiciones, o conflictos comunales)."},
91
+ "conclusion_final": {"type": "string", "description": "Conclusi贸n final sobre la validez del t铆tulo o derecho y aptitud para una transacci贸n."}
92
+ },
93
+ "required": ["tipo_documento_detectado", "propietario_actual", "conclusion_final"]
94
+ }
95
+
96
  prompt_text = (
97
+ "Eres un **experto legal en derecho inmobiliario colombiano y reglamentos de Juntas de Acci贸n Comunal (JAC)**. "
98
+ "Tu tarea es analizar el documento adjunto (que puede ser un Certificado de Tradici贸n y Libertad o un documento de JAC) y extraer la informaci贸n clave para un estudio de t铆tulos o de propiedad comunal. "
99
+ "DEBES DEVOLVER TU RESPUESTA EXCLUSIVAMENTE EN FORMATO JSON, siguiendo el esquema proporcionado. No a帽adas texto explicativo fuera del JSON."
100
+ f"\n\nTexto del Documento:\n\n{text}"
 
 
 
 
 
 
 
 
 
101
  )
102
 
103
  response = client.chat.completions.create(
104
+ model="gpt-4o-mini", # Usamos GPT-4o-mini, que es mejor para tareas de JSON y r谩pido.
105
  messages=[
106
  {"role": "system", "content": prompt_text}
107
  ],
108
+ # Configuramos la respuesta para que sea JSON con el esquema definido
109
+ response_format={"type": "json_object", "schema": json_schema},
110
+ temperature=0.3,
111
  )
112
 
113
+ # El contenido de la respuesta ser谩 una cadena JSON
114
+ json_string = response.choices[0].message.content.strip()
115
+
116
+ # Intentamos parsear la cadena JSON a un objeto Python
117
+ import json
118
+ structured_data = json.loads(json_string)
119
+
120
+ # Devolvemos el objeto structured_data
121
+ return structured_data
122
 
123
  except Exception as e:
124
+ print(f"Error al generar el resumen/JSON con OpenAI: {e}")
125
+ # Capturamos errores de la API o de formato JSON inv谩lido
126
+ raise Exception("Error al generar el JSON. Verifica que la APIKey o que el documento sea legible y contenga texto relevante.")
127
 
128
  # --- Rutas de Flask ---
129
 
 
144
  raw_text = extract_text_from_file(file)
145
 
146
  if not raw_text:
147
+ return jsonify({'error': 'No se pudo extraer texto. Documento ilegible o sin texto.'}), 400
148
 
149
+ # Llamamos a la funci贸n que ahora devuelve un objeto Python (diccionario)
150
+ structured_summary = generate_summary_openai(raw_text)
151
+
152
+ # Convertimos el diccionario a una lista de cadenas para mostrar en el frontend
153
+ summary_list = [f"**{k.replace('_', ' ').title()}:** {v}" for k, v in structured_summary.items()]
154
+
155
+ return jsonify({'summary': summary_list})
156
 
157
  except Exception as e:
158
  print(f"Error general en la ruta /summarize: {e}")
 
159
  return jsonify({'error': f"Error interno del servidor: {str(e)}"}), 500
160
 
161
  if __name__ == '__main__':