From b7707618322f3809831166efc2c1df6816ed023a Mon Sep 17 00:00:00 2001 From: erik Date: Sat, 11 Jul 2026 00:27:05 -0500 Subject: [PATCH] feat: Enhance CSV cleaning service with new endpoints for CSV analysis and preview functionality. Introduce support for job management linked to extraction batches, and update the cleaning rules structure. Add new utility functions for handling CSV data and improve documentation for clarity on usage and available endpoints. --- .gitignore | 1 + docs/database.dbml | 10 +- services/api_gateway/api/main.py | 8 +- services/cleaning/README.md | 17 +- services/cleaning/api/main.py | 388 ++- services/cleaning/domain/csv_cleaning.py | 2130 ----------------- .../cleaning/domain/csv_cleaning/__init__.py | 64 + .../cleaning/domain/csv_cleaning/analyze.py | 94 + .../cleaning/domain/csv_cleaning/apply.py | 105 + .../domain/csv_cleaning/column_hints.py | 210 ++ .../cleaning/domain/csv_cleaning/cop_mod.py | 162 ++ .../cleaning/domain/csv_cleaning/dates.py | 459 ++++ .../cleaning/domain/csv_cleaning/documents.py | 175 ++ services/cleaning/domain/csv_cleaning/io.py | 132 + .../cleaning/domain/csv_cleaning/models.py | 67 + .../cleaning/domain/csv_cleaning/notas.py | 73 + .../cleaning/domain/csv_cleaning/nullish.py | 24 + .../cleaning/domain/csv_cleaning/numbers.py | 258 ++ .../domain/csv_cleaning/rules/__init__.py | 13 + .../domain/csv_cleaning/rules/builtins.py | 135 ++ .../domain/csv_cleaning/rules/dates.py | 141 ++ .../domain/csv_cleaning/rules/documents.py | 23 + .../domain/csv_cleaning/rules/numeric.py | 57 + .../domain/csv_cleaning/rules/registry.py | 80 + .../domain/csv_cleaning/rules/special.py | 8 + .../domain/csv_cleaning/rules/text.py | 40 + .../domain/csv_cleaning/type_inference.py | 143 ++ services/cleaning/infrastructure/database.py | 28 +- services/cleaning/requirements.txt | 1 + .../versions/0003_cleaning_csv_and_job_id.py | 90 + services/desktop_app/.env.minio.example | 11 + services/desktop_app/README.md | 52 +- services/desktop_app/electron/autoUpdate.cjs | 224 ++ services/desktop_app/electron/icon.png | Bin 0 -> 56966 bytes services/desktop_app/electron/main.cjs | 37 + services/desktop_app/electron/preload.cjs | 7 + services/desktop_app/index.html | 1 + services/desktop_app/package-lock.json | 401 +++- services/desktop_app/package.json | 18 +- services/desktop_app/public/app-icon.png | Bin 0 -> 165484 bytes .../desktop_app/resources/icon-source.png | Bin 0 -> 53035 bytes services/desktop_app/resources/icon.ico | Bin 0 -> 91063 bytes services/desktop_app/resources/icon.png | Bin 0 -> 165484 bytes .../desktop_app/scripts/publish-version.mjs | 188 ++ .../desktop_app/src/renderer/App.test.tsx | 167 +- services/desktop_app/src/renderer/App.tsx | 245 +- .../src/renderer/assets/app-icon.png | Bin 0 -> 56966 bytes .../components/BackgroundProgressBanner.tsx | 5 +- .../renderer/components/CleaningStepper.tsx | 44 + .../renderer/components/CountingWizard.tsx | 30 +- .../components/ExtractionResultsTable.tsx | 147 ++ .../renderer/components/ExtractionWizard.tsx | 69 +- .../src/renderer/components/UpdatePrompt.tsx | 172 ++ .../desktop_app/src/renderer/lib/apiClient.ts | 56 +- .../src/renderer/lib/cleaningSettings.test.ts | 32 + .../src/renderer/lib/cleaningSettings.ts | 66 + .../desktop_app/src/renderer/lib/csv.test.ts | 30 +- services/desktop_app/src/renderer/lib/csv.ts | 62 +- .../modules/cleaning/CleaningModule.ts | 18 + .../modules/cleaning/CleaningResults.tsx | 125 + .../modules/cleaning/CleaningWizard.tsx | 967 ++++++++ .../modules/cleaning/CleaningWizardModule.tsx | 26 + .../modules/counting/CountingResults.tsx | 28 +- .../modules/extraction/ExtractionResults.tsx | 89 +- .../extraction/JobExtractionWizard.tsx | 149 +- services/desktop_app/src/renderer/styles.css | 789 +++++- .../src/renderer/types/jobSection.ts | 2 +- .../src/renderer/views/JobResultsView.tsx | 277 ++- .../src/renderer/views/JobView.tsx | 22 +- .../src/renderer/views/SettingsView.tsx | 471 ++++ services/desktop_app/src/shared/types.ts | 107 +- services/extraction/api/main.py | 3 +- .../extraction/infrastructure/database.py | 12 +- 73 files changed, 7614 insertions(+), 2671 deletions(-) delete mode 100644 services/cleaning/domain/csv_cleaning.py create mode 100644 services/cleaning/domain/csv_cleaning/__init__.py create mode 100644 services/cleaning/domain/csv_cleaning/analyze.py create mode 100644 services/cleaning/domain/csv_cleaning/apply.py create mode 100644 services/cleaning/domain/csv_cleaning/column_hints.py create mode 100644 services/cleaning/domain/csv_cleaning/cop_mod.py create mode 100644 services/cleaning/domain/csv_cleaning/dates.py create mode 100644 services/cleaning/domain/csv_cleaning/documents.py create mode 100644 services/cleaning/domain/csv_cleaning/io.py create mode 100644 services/cleaning/domain/csv_cleaning/models.py create mode 100644 services/cleaning/domain/csv_cleaning/notas.py create mode 100644 services/cleaning/domain/csv_cleaning/nullish.py create mode 100644 services/cleaning/domain/csv_cleaning/numbers.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/__init__.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/builtins.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/dates.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/documents.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/numeric.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/registry.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/special.py create mode 100644 services/cleaning/domain/csv_cleaning/rules/text.py create mode 100644 services/cleaning/domain/csv_cleaning/type_inference.py create mode 100644 services/db/alembic/versions/0003_cleaning_csv_and_job_id.py create mode 100644 services/desktop_app/.env.minio.example create mode 100644 services/desktop_app/electron/autoUpdate.cjs create mode 100644 services/desktop_app/electron/icon.png create mode 100644 services/desktop_app/public/app-icon.png create mode 100644 services/desktop_app/resources/icon-source.png create mode 100644 services/desktop_app/resources/icon.ico create mode 100644 services/desktop_app/resources/icon.png create mode 100644 services/desktop_app/scripts/publish-version.mjs create mode 100644 services/desktop_app/src/renderer/assets/app-icon.png create mode 100644 services/desktop_app/src/renderer/components/CleaningStepper.tsx create mode 100644 services/desktop_app/src/renderer/components/ExtractionResultsTable.tsx create mode 100644 services/desktop_app/src/renderer/components/UpdatePrompt.tsx create mode 100644 services/desktop_app/src/renderer/lib/cleaningSettings.test.ts create mode 100644 services/desktop_app/src/renderer/lib/cleaningSettings.ts create mode 100644 services/desktop_app/src/renderer/modules/cleaning/CleaningModule.ts create mode 100644 services/desktop_app/src/renderer/modules/cleaning/CleaningResults.tsx create mode 100644 services/desktop_app/src/renderer/modules/cleaning/CleaningWizard.tsx create mode 100644 services/desktop_app/src/renderer/modules/cleaning/CleaningWizardModule.tsx create mode 100644 services/desktop_app/src/renderer/views/SettingsView.tsx diff --git a/.gitignore b/.gitignore index aea078a..70546ac 100644 --- a/.gitignore +++ b/.gitignore @@ -140,6 +140,7 @@ celerybeat.pid # Environments .env +.env.minio .venv env/ venv/ diff --git a/docs/database.dbml b/docs/database.dbml index 2e29b1b..58929a0 100644 --- a/docs/database.dbml +++ b/docs/database.dbml @@ -167,7 +167,9 @@ Table extraction_rows { Table cleaning_jobs { id varchar(36) [pk, note: 'UUID v4'] - extraction_batch_id varchar(36) [not null, ref: > extraction_batches.id] + extraction_batch_id varchar(36) [null, ref: > extraction_batches.id, note: 'Null si source=csv'] + job_id varchar(36) [null, ref: > jobs.id, note: 'Trabajo Desktop al que pertenece'] + source varchar(32) [not null, default: 'extraction', note: 'extraction | csv'] status varchar(32) [not null, default: 'pending', note: 'pending | running | completed | failed'] request jsonb [not null, default: '{}', note: 'CleaningRequest (rules_per_column, etc.)'] log jsonb [not null, default: '[]', note: 'Lista de mensajes de auditoría del job'] @@ -177,16 +179,18 @@ Table cleaning_jobs { indexes { extraction_batch_id + job_id + source status } - Note: 'Ejecución de reglas de limpieza sobre las filas de un lote de extracción.' + Note: 'Ejecución de reglas de limpieza sobre filas de extracción o CSV subido.' } Table cleaned_rows { id integer [pk, increment] cleaning_job_id varchar(36) [not null, ref: > cleaning_jobs.id] - extraction_batch_id varchar(36) [not null, ref: > extraction_batches.id] + extraction_batch_id varchar(36) [null, ref: > extraction_batches.id] row_index integer [not null, default: 0, note: 'Conserva el orden del lote original'] row_json jsonb [not null, default: '{}', note: 'Fila tras aplicar reglas de limpieza'] created_at timestamptz [not null] diff --git a/services/api_gateway/api/main.py b/services/api_gateway/api/main.py index 3048722..b5960d5 100644 --- a/services/api_gateway/api/main.py +++ b/services/api_gateway/api/main.py @@ -66,7 +66,13 @@ def _client() -> httpx.AsyncClient: def _is_upload_path(path: str) -> bool: - return path.startswith("/count/") or "/ingest" in path or "/upload" in path + return ( + path.startswith("/count/") + or "/ingest" in path + or "/upload" in path + or path.startswith("/cleaning/analyze-csv") + or path.startswith("/cleaning/jobs/from-csv") + ) def _timeout_for_path(path: str) -> httpx.Timeout: diff --git a/services/cleaning/README.md b/services/cleaning/README.md index e5b2b77..57138cb 100644 --- a/services/cleaning/README.md +++ b/services/cleaning/README.md @@ -18,7 +18,11 @@ Recibe lotes de filas extraídas por el servicio `extraction` y aplica reglas co | `GET` | `/health` | Liveness | | `GET` | `/rules` | Lista de reglas de limpieza disponibles | | `GET` | `/batches/{batch_id}/analysis` | Análisis de columnas de un lote de extracción | -| `POST` | `/jobs` | Crear job de limpieza | +| `POST` | `/analyze-csv` | Análisis de columnas desde CSV (multipart) | +| `POST` | `/preview` | Vista previa Antes/Después sobre muestra (sin persistir) | +| `POST` | `/jobs` | Limpieza desde `extraction_batch_id` (+ `job_id` opcional) | +| `POST` | `/jobs/from-csv` | Limpieza desde CSV (multipart + reglas JSON) | +| `GET` | `/jobs?job_id=` | Listar jobs (filtro opcional por trabajo Desktop) | | `GET` | `/jobs/{job_id}` | Estado y detalle del job | | `GET` | `/jobs/{job_id}/rows` | Filas limpias del job | @@ -44,7 +48,7 @@ Las reglas disponibles se listan en `GET /rules`. ## Reglas de limpieza -Definidas en `domain/csv_cleaning.py`. Cada regla es una función que transforma los valores de una columna. Se pueden encadenar múltiples reglas por columna. +Definidas en el paquete `domain/csv_cleaning/` (registro en `rules/builtins.py`). Cada regla transforma los valores de una columna; se pueden encadenar varias por columna. Para agregar una regla nueva, ver `rules/registry.py`. --- @@ -72,11 +76,14 @@ cleaning_jobs — job de limpieza (FK → extraction_batches) ``` api/ - main.py — FastAPI app con todos los endpoints + main.py — FastAPI app con todos los endpoints domain/ - csv_cleaning.py — reglas de limpieza y función de análisis + csv_cleaning/ — paquete de limpieza (análisis + reglas) + analyze.py — resumen por columna + apply.py — aplicación de reglas + rules/ — registro e implementaciones de reglas infrastructure/ - database.py — modelos SQLAlchemy y sesión + database.py — modelos SQLAlchemy y sesión Dockerfile requirements.txt ``` diff --git a/services/cleaning/api/main.py b/services/cleaning/api/main.py index 505da45..dc7e365 100644 --- a/services/cleaning/api/main.py +++ b/services/cleaning/api/main.py @@ -6,16 +6,24 @@ import json from typing import Any import pandas as pd -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, File, Form, HTTPException, UploadFile from pydantic import BaseModel, Field from sqlalchemy import delete, select -from domain.csv_cleaning import CLEANING_RULES, analyze_dataframe, apply_rules +from domain.csv_cleaning import ( + CLEANING_RULES, + analyze_dataframe, + apply_rules, + load_csv_bytes, + sample_dataframe_rows, +) from infrastructure.database import ( CleanedRow, CleaningJob, ExtractionBatch, ExtractionRow, + Job, + Task, init_db, session_scope, utcnow, @@ -36,12 +44,22 @@ app = FastAPI( class CleaningRequest(BaseModel): - extraction_batch_id: str + extraction_batch_id: str | None = None + job_id: str | None = None rules_per_column: dict[str, list[str]] = Field(default_factory=dict) cop_mod_fecha_column: dict[str, str] = Field(default_factory=dict) cop_mod_config: dict[str, Any] | None = None +class PreviewRequest(BaseModel): + rows: list[dict[str, Any]] = Field(default_factory=list) + extraction_batch_id: str | None = None + rules_per_column: dict[str, list[str]] = Field(default_factory=dict) + cop_mod_fecha_column: dict[str, str] = Field(default_factory=dict) + cop_mod_config: dict[str, Any] | None = None + sample_size: int = 8 + + def dataframe_to_records(df: pd.DataFrame) -> list[dict[str, Any]]: return json.loads(df.to_json(orient="records", date_format="iso")) @@ -50,6 +68,8 @@ def serialize_job(job: CleaningJob) -> dict[str, Any]: return { "id": job.id, "extraction_batch_id": job.extraction_batch_id, + "job_id": job.job_id, + "source": job.source, "status": job.status, "request": job.request, "log": job.log, @@ -59,6 +79,144 @@ def serialize_job(job: CleaningJob) -> dict[str, Any]: } +def _validate_job_id(session: Any, job_id: str | None) -> None: + if not job_id: + return + if session.get(Job, job_id) is None: + raise HTTPException(status_code=404, detail="Trabajo (job) no encontrado") + + +def _create_cleaning_task( + session: Any, + *, + job_id: str | None, + cleaning_job_id: str, + source: str, + status: str, + request: dict[str, Any], + error: str | None = None, + title: str | None = None, +) -> str | None: + """Crea Task(type=cleaning) vinculada al job Desktop. Devuelve task.id o None.""" + if not job_id: + return None + now = utcnow() + task = Task( + job_id=job_id, + type="cleaning", + status=status, + title=title or f"Limpieza ({source})", + request=request, + result={"cleaning_job_id": cleaning_job_id}, + error=error, + started_at=now, + finished_at=now, + updated_at=now, + ) + session.add(task) + session.flush() + return task.id + + +def _persist_cleaned_rows( + session: Any, + *, + job_id: str, + extraction_batch_id: str | None, + output_rows: list[dict[str, Any]], +) -> None: + session.execute(delete(CleanedRow).where(CleanedRow.cleaning_job_id == job_id)) + for idx, row in enumerate(output_rows, start=1): + session.add( + CleanedRow( + cleaning_job_id=job_id, + extraction_batch_id=extraction_batch_id, + row_index=idx, + row_json=row, + ) + ) + + +def _run_rules_on_dataframe( + df: pd.DataFrame, + rules_per_column: dict[str, list[str]], + cop_mod_fecha_column: dict[str, str], + cop_mod_config: dict[str, Any] | None, +) -> tuple[list[dict[str, Any]], list[str]]: + result_df, log = apply_rules( + df, + rules_per_column, + cop_mod_fecha_column=cop_mod_fecha_column, + cop_mod_config=cop_mod_config, + ) + return dataframe_to_records(result_df), log + + +def _parse_json_form(raw: str | None, default: Any) -> Any: + if not raw or not raw.strip(): + return default + try: + return json.loads(raw) + except json.JSONDecodeError as exc: + raise HTTPException(status_code=400, detail=f"JSON inválido: {exc}") from exc + + +def _analysis_payload(df: pd.DataFrame, **extra: Any) -> dict[str, Any]: + return { + **extra, + "row_count": len(df), + "columns": [summary.__dict__ for summary in analyze_dataframe(df)], + "sample_rows": sample_dataframe_rows(df, 8), + } + + +def _cell_str(value: Any) -> str: + if value is None: + return "" + text = str(value) + if text in ("None", "nan", "NaT"): + return "" + if len(text) > 80: + return text[:77] + "..." + return text + + +def _preview_from_dataframe( + df: pd.DataFrame, + rules_per_column: dict[str, list[str]], + cop_mod_fecha_column: dict[str, str], + cop_mod_config: dict[str, Any] | None, +) -> dict[str, Any]: + rows_before = dataframe_to_records(df) + result_df, _log = apply_rules( + df, + rules_per_column, + cop_mod_fecha_column=cop_mod_fecha_column, + cop_mod_config=cop_mod_config, + ) + rows_after = dataframe_to_records(result_df) + + # Emparejar por índice de fila de muestra (misma posición) + n = min(len(rows_before), len(rows_after)) + all_cols = list(dict.fromkeys([*df.columns.astype(str), *result_df.columns.astype(str)])) + columns_preview: list[dict[str, Any]] = [] + for col in all_cols: + before_vals = [_cell_str(rows_before[i].get(col)) for i in range(n)] + after_vals = [_cell_str(rows_after[i].get(col)) for i in range(n)] + columns_preview.append({ + "name": col, + "before": before_vals, + "after": after_vals, + "changed": before_vals != after_vals, + }) + + return { + "rows_before": rows_before, + "rows_after": rows_after, + "columns": columns_preview, + } + + @app.get("/health") def health() -> dict[str, str]: return {"status": "ok"} @@ -90,18 +248,75 @@ def analyze_batch(batch_id: str) -> dict[str, Any]: records = [row[0] for row in rows] df = pd.DataFrame(records) - return { - "batch_id": batch_id, - "columns": [summary.__dict__ for summary in analyze_dataframe(df)], - } + return _analysis_payload(df, batch_id=batch_id) + + +@app.post("/analyze-csv") +async def analyze_csv(file: UploadFile = File(...)) -> dict[str, Any]: + content = await file.read() + if not content: + raise HTTPException(status_code=400, detail="El archivo CSV está vacío") + try: + df, delimiter = load_csv_bytes(content, file.filename or "upload.csv") + except Exception as exc: # noqa: BLE001 + raise HTTPException(status_code=400, detail=f"No se pudo leer el CSV: {exc}") from exc + if df.empty: + raise HTTPException(status_code=400, detail="El CSV no tiene filas") + return _analysis_payload(df, filename=file.filename, delimiter=delimiter) + + +@app.post("/preview") +def preview_cleaning(request: PreviewRequest) -> dict[str, Any]: + """Aplica reglas sobre una muestra pequeña y devuelve Antes/Después.""" + records: list[dict[str, Any]] = list(request.rows or []) + + if not records and request.extraction_batch_id: + with session_scope() as session: + batch = session.get(ExtractionBatch, request.extraction_batch_id) + if batch is None: + raise HTTPException(status_code=404, detail="Lote de extraccion no encontrado") + rows = session.execute( + select(ExtractionRow.row_json) + .where(ExtractionRow.batch_id == request.extraction_batch_id) + .order_by(ExtractionRow.row_index, ExtractionRow.id) + ).all() + all_records = [row[0] for row in rows] + if not all_records: + raise HTTPException(status_code=400, detail="El lote no tiene filas") + df_full = pd.DataFrame(all_records) + records = sample_dataframe_rows(df_full, max(1, min(request.sample_size, 20))) + + if not records: + raise HTTPException( + status_code=400, + detail="Se requieren rows o extraction_batch_id con filas", + ) + + df = pd.DataFrame(records) + try: + return _preview_from_dataframe( + df, + request.rules_per_column, + request.cop_mod_fecha_column, + request.cop_mod_config, + ) + except Exception as exc: # noqa: BLE001 + raise HTTPException(status_code=500, detail=str(exc)) from exc @app.post("/jobs") def run_cleaning(request: CleaningRequest) -> dict[str, Any]: + if not request.extraction_batch_id: + raise HTTPException( + status_code=400, + detail="extraction_batch_id es requerido (use /jobs/from-csv para CSV)", + ) + with session_scope() as session: batch = session.get(ExtractionBatch, request.extraction_batch_id) if batch is None: raise HTTPException(status_code=404, detail="Lote de extraccion no encontrado") + _validate_job_id(session, request.job_id) extracted_rows = session.execute( select(ExtractionRow.row_json) @@ -114,6 +329,8 @@ def run_cleaning(request: CleaningRequest) -> dict[str, Any]: job = CleaningJob( extraction_batch_id=request.extraction_batch_id, + job_id=request.job_id, + source="extraction", status="running", request=request.model_dump(), updated_at=utcnow(), @@ -121,36 +338,45 @@ def run_cleaning(request: CleaningRequest) -> dict[str, Any]: session.add(job) session.flush() job_id = job.id + batch_id = request.extraction_batch_id + desktop_job_id = request.job_id + request_dump = request.model_dump() try: df = pd.DataFrame(records) - result_df, log = apply_rules( + output_rows, log = _run_rules_on_dataframe( df, request.rules_per_column, - cop_mod_fecha_column=request.cop_mod_fecha_column, - cop_mod_config=request.cop_mod_config, + request.cop_mod_fecha_column, + request.cop_mod_config, ) - output_rows = dataframe_to_records(result_df) with session_scope() as session: - session.execute(delete(CleanedRow).where(CleanedRow.cleaning_job_id == job_id)) - for idx, row in enumerate(output_rows, start=1): - session.add( - CleanedRow( - cleaning_job_id=job_id, - extraction_batch_id=request.extraction_batch_id, - row_index=idx, - row_json=row, - ) - ) + _persist_cleaned_rows( + session, + job_id=job_id, + extraction_batch_id=batch_id, + output_rows=output_rows, + ) job = session.get(CleaningJob, job_id) if job is None: raise RuntimeError("No se encontro el job de limpieza recien creado.") job.status = "completed" job.log = log job.updated_at = utcnow() + _create_cleaning_task( + session, + job_id=desktop_job_id, + cleaning_job_id=job_id, + source="extraction", + status="completed", + request=request_dump, + title="Limpieza desde extracción", + ) session.flush() return serialize_job(job) + except HTTPException: + raise except Exception as exc: with session_scope() as session: job = session.get(CleaningJob, job_id) @@ -158,15 +384,127 @@ def run_cleaning(request: CleaningRequest) -> dict[str, Any]: job.status = "failed" job.error = str(exc) job.updated_at = utcnow() + _create_cleaning_task( + session, + job_id=desktop_job_id, + cleaning_job_id=job_id, + source="extraction", + status="failed", + request=request_dump, + error=str(exc), + title="Limpieza desde extracción", + ) + raise HTTPException(status_code=500, detail=str(exc)) from exc + + +@app.post("/jobs/from-csv") +async def run_cleaning_from_csv( + file: UploadFile = File(...), + job_id: str | None = Form(None), + rules_per_column: str | None = Form(None), + cop_mod_fecha_column: str | None = Form(None), + cop_mod_config: str | None = Form(None), +) -> dict[str, Any]: + content = await file.read() + if not content: + raise HTTPException(status_code=400, detail="El archivo CSV está vacío") + + rules = _parse_json_form(rules_per_column, {}) + fecha_map = _parse_json_form(cop_mod_fecha_column, {}) + cop_cfg = _parse_json_form(cop_mod_config, None) + if not isinstance(rules, dict): + raise HTTPException(status_code=400, detail="rules_per_column debe ser un objeto JSON") + if not isinstance(fecha_map, dict): + raise HTTPException(status_code=400, detail="cop_mod_fecha_column debe ser un objeto JSON") + + try: + df, delimiter = load_csv_bytes(content, file.filename or "upload.csv") + except Exception as exc: # noqa: BLE001 + raise HTTPException(status_code=400, detail=f"No se pudo leer el CSV: {exc}") from exc + if df.empty: + raise HTTPException(status_code=400, detail="El CSV no tiene filas") + + request_dump: dict[str, Any] = { + "source": "csv", + "filename": file.filename, + "delimiter": delimiter, + "job_id": job_id, + "rules_per_column": rules, + "cop_mod_fecha_column": fecha_map, + "cop_mod_config": cop_cfg, + } + + with session_scope() as session: + _validate_job_id(session, job_id) + job = CleaningJob( + extraction_batch_id=None, + job_id=job_id, + source="csv", + status="running", + request=request_dump, + updated_at=utcnow(), + ) + session.add(job) + session.flush() + cleaning_job_id = job.id + desktop_job_id = job_id + + try: + output_rows, log = _run_rules_on_dataframe(df, rules, fecha_map, cop_cfg) + + with session_scope() as session: + _persist_cleaned_rows( + session, + job_id=cleaning_job_id, + extraction_batch_id=None, + output_rows=output_rows, + ) + job = session.get(CleaningJob, cleaning_job_id) + if job is None: + raise RuntimeError("No se encontro el job de limpieza recien creado.") + job.status = "completed" + job.log = log + job.updated_at = utcnow() + _create_cleaning_task( + session, + job_id=desktop_job_id, + cleaning_job_id=cleaning_job_id, + source="csv", + status="completed", + request=request_dump, + title=f"Limpieza CSV — {file.filename or 'upload.csv'}", + ) + session.flush() + return serialize_job(job) + except HTTPException: + raise + except Exception as exc: + with session_scope() as session: + job = session.get(CleaningJob, cleaning_job_id) + if job is not None: + job.status = "failed" + job.error = str(exc) + job.updated_at = utcnow() + _create_cleaning_task( + session, + job_id=desktop_job_id, + cleaning_job_id=cleaning_job_id, + source="csv", + status="failed", + request=request_dump, + error=str(exc), + title=f"Limpieza CSV — {file.filename or 'upload.csv'}", + ) raise HTTPException(status_code=500, detail=str(exc)) from exc @app.get("/jobs") -def list_jobs() -> list[dict[str, Any]]: +def list_jobs(job_id: str | None = None) -> list[dict[str, Any]]: with session_scope() as session: - jobs = session.scalars( - select(CleaningJob).order_by(CleaningJob.created_at.desc()) - ).all() + stmt = select(CleaningJob).order_by(CleaningJob.created_at.desc()) + if job_id: + stmt = stmt.where(CleaningJob.job_id == job_id) + jobs = session.scalars(stmt).all() return [serialize_job(job) for job in jobs] diff --git a/services/cleaning/domain/csv_cleaning.py b/services/cleaning/domain/csv_cleaning.py deleted file mode 100644 index 8371708..0000000 --- a/services/cleaning/domain/csv_cleaning.py +++ /dev/null @@ -1,2130 +0,0 @@ -"""Limpieza de CSV con análisis inteligente local y reglas configurables. - -Este módulo es pura lógica (sin GUI, sin LLM). Provee: -- `analyze_dataframe` para obtener un resumen por columna (tipo inferido, - ejemplo aleatorio, nulos, únicos, estadísticas específicas por tipo). -- Un registro `CLEANING_RULES` con reglas aplicables a columnas. -- `apply_rules` que aplica las reglas seleccionadas por columna y devuelve - el DataFrame resultante junto con un log de operaciones/advertencias. -""" -from __future__ import annotations - -import csv as _csv -import logging -import math -import random -import re -import unicodedata -from collections import Counter -from dataclasses import dataclass, field -from datetime import date -from typing import Any, Callable, Literal, Optional - -import numpy as np -import pandas as pd - -logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- -# CUOTA MODERADORA (COP/MOD) -# --------------------------------------------------------------------------- - -DEFAULT_MODERADORA_BY_YEAR: dict[int, set[int]] = { - 2023: {4100, 16400, 43000}, - 2024: {4500, 18200, 47700}, - 2025: {4700, 19200, 50300}, - 2026: {5000, 20100, 52800}, -} - - -def normalize_moderadora_by_year_dict(raw: Any) -> dict[int, set[int]]: - """Normaliza un dict JSON año → lista de enteros (cuotas moderadora).""" - if not isinstance(raw, dict): - return {} - out: dict[int, set[int]] = {} - for k, v in raw.items(): - try: - yi = int(k) - except (TypeError, ValueError): - continue - if not isinstance(v, (list, tuple)): - continue - nums: set[int] = set() - for x in v: - try: - nums.add(int(round(float(x)))) - except (TypeError, ValueError): - continue - if nums: - out[yi] = nums - return out - - -def merge_moderadora_config( - cop_mod_config: Optional[dict[str, Any]], -) -> dict[int, set[int]]: - """Fusiona defaults con `moderadora_by_year` opcional del usuario.""" - merged = {y: set(vals) for y, vals in DEFAULT_MODERADORA_BY_YEAR.items()} - if cop_mod_config and isinstance(cop_mod_config.get("moderadora_by_year"), dict): - custom = normalize_moderadora_by_year_dict( - cop_mod_config["moderadora_by_year"] - ) - for y, vals in custom.items(): - merged[y] = set(vals) - return merged - - -def _alloc_cop_mod_column_names( - col_name: str, existing: set[str] -) -> tuple[str, str]: - """Nombres únicos para copago y moderadora.""" - base = re.sub(r"[^\w]+", "_", str(col_name).strip(), flags=re.ASCII) or "col" - for i in range(0, 200): - suf = f"_{i}" if i else "" - ccol, mcol = f"{base}{suf}_copago", f"{base}{suf}_moderadora" - if ccol not in existing and mcol not in existing: - return ccol, mcol - raise ValueError("No se pudieron asignar nombres de columnas únicos.") - - -def _amount_cell_to_optional_int( - val: Any, - decimal_hint: Optional[Literal[",", "."]], -) -> Optional[int]: - try: - f = _parse_lenient_one(val, decimal_hint) - except Exception: - return None - if f is None or (isinstance(f, float) and not np.isfinite(f)) or ( - isinstance(f, (float, np.floating)) and np.isnan(f) - ): - return None - try: - return int(round(float(f))) - except (TypeError, ValueError, OverflowError): - return None - - -def _apply_cop_mod_split( - result: pd.DataFrame, - amount_col: str, - fecha_column: str, - log: list[str], - *, - moderadora_by_year: dict[int, set[int]], -) -> None: - """Añade columnas copago y moderadora según cuotas por año de la fecha.""" - if fecha_column not in result.columns: - log.append( - f"[WARN] COP/MOD: columna de fecha '{fecha_column}' no existe; se omite." - ) - return - if amount_col not in result.columns: - log.append(f"[WARN] COP/MOD: columna de importe '{amount_col}' no existe; se omite.") - return - - fec_ser = result[fecha_column] - amt_ser = result[amount_col] - stripped = fec_ser.astype(str).str.replace(r"[\s\/\-\.]", "", regex=True) - stripped = stripped.where(fec_ser.notna(), other=pd.NA) - sep_fmt = detect_date_format(fec_ser) or detect_date_format(stripped) - compact8_fmt, compact6_fmt = _detect_compact_date_formats(fec_ser) - amt_hint = _infer_decimal_hint_from_column(amt_ser.astype(str)) - - ccol, mcol = _alloc_cop_mod_column_names(str(amount_col), set(result.columns)) - cop_vals: list[Any] = [] - mod_vals: list[Any] = [] - - for i in range(len(result)): - amt_raw = amt_ser.iloc[i] - fec_raw = fec_ser.iloc[i] - amt_i = _amount_cell_to_optional_int(amt_raw, amt_hint) - if amt_i is None: - cop_vals.append(0) - mod_vals.append(0) - continue - - parsed_d = _parse_any_date_to_tuple( - fec_raw, - sep_fmt=sep_fmt, - compact8_fmt=compact8_fmt, - compact6_fmt=compact6_fmt, - ) - if parsed_d is None: - cop_vals.append(amt_i) - mod_vals.append(0) - continue - _d, _m, year = parsed_d - tiers = moderadora_by_year.get(year) - if tiers is None: - cop_vals.append(amt_i) - mod_vals.append(0) - continue - - if amt_i in tiers: - cop_vals.append(0) - mod_vals.append(amt_i) - else: - cop_vals.append(amt_i) - mod_vals.append(0) - - result[ccol] = cop_vals - result[mcol] = mod_vals - log.append( - f"[OK] '{amount_col}' -> '{ccol}', '{mcol}' <- Separa COP/MOD " - f"(fecha: '{fecha_column}')" - ) - - -# --------------------------------------------------------------------------- -# MODELO DE DATOS -# --------------------------------------------------------------------------- - -TYPE_INTEGER = "integer" -TYPE_DECIMAL = "decimal" -TYPE_DATE = "date" -TYPE_BOOLEAN = "boolean" -TYPE_CATEGORICAL = "categorical" -TYPE_TEXT = "text" -TYPE_MIXED = "mixed" -TYPE_EMPTY = "empty" - - -@dataclass -class ColumnSummary: - """Resumen inteligente para una columna del DataFrame.""" - name: str - inferred_type: str - example: str = "" - null_count: int = 0 - null_pct: float = 0.0 - unique_count: int = 0 - unique_pct: float = 0.0 - min: Optional[float] = None - max: Optional[float] = None - mean: Optional[float] = None - avg_length: Optional[float] = None - top_values: list[tuple[str, int]] = field(default_factory=list) - detected_date_format: Optional[str] = None - applicable_rules: list[str] = field(default_factory=list) - - def info_text(self) -> str: - """Texto humano con información adicional (para subtítulo de la UI).""" - parts: list[str] = [] - parts.append(f"nulos: {self.null_count} ({self.null_pct:.0f}%)") - parts.append(f"únicos: {self.unique_count}") - if self.inferred_type in (TYPE_INTEGER, TYPE_DECIMAL): - if self.min is not None: - parts.append(f"min: {_fmt_num(self.min)}") - if self.max is not None: - parts.append(f"max: {_fmt_num(self.max)}") - if self.mean is not None: - parts.append(f"media: {_fmt_num(self.mean)}") - elif self.inferred_type == TYPE_DATE: - if self.detected_date_format: - parts.append(f"formato: {self.detected_date_format}") - elif self.inferred_type in (TYPE_TEXT, TYPE_CATEGORICAL): - if self.avg_length is not None: - parts.append(f"long. prom: {self.avg_length:.1f}") - if self.top_values: - tops = ", ".join(f'"{v}" ({c})' for v, c in self.top_values[:3]) - parts.append(f"top: {tops}") - return " · ".join(parts) - - -def _fmt_num(x: float) -> str: - if x is None: - return "" - if isinstance(x, float) and x.is_integer(): - return str(int(x)) - if isinstance(x, float): - return f"{x:.2f}" - return str(x) - - -# --------------------------------------------------------------------------- -# DETECCIÓN DE FECHAS -# --------------------------------------------------------------------------- - -_DATE_NUM_RE = re.compile(r"^\s*(\d{1,4})[\s\/\-\.](\d{1,2})[\s\/\-\.](\d{1,4})\s*$") -_DATE_COMPACT_RE = re.compile(r"^\s*(\d{6}|\d{8})\s*$") -# Hora opcional: HH[:MM[:SS]][ AM|PM] al final de la cadena -_TIME_TAIL_RE = re.compile( - r"[\sT]+(\d{1,2})(?::(\d{1,2})(?::(\d{1,2}))?)?\s*([APap]\.?\s*[Mm]\.?)?\s*$" -) - -_MONTH_NAME_TO_NUM = { - "enero": 1, - "ene": 1, - "febrero": 2, - "feb": 2, - "marzo": 3, - "mar": 3, - "abril": 4, - "abr": 4, - "mayo": 5, - "may": 5, - "junio": 6, - "jun": 6, - "julio": 7, - "jul": 7, - "agosto": 8, - "ago": 8, - "septiembre": 9, - "setiembre": 9, - "sep": 9, - "sept": 9, - "octubre": 10, - "oct": 10, - "noviembre": 11, - "nov": 11, - "diciembre": 12, - "dic": 12, -} - - -def _split_date_time(value: str) -> tuple[str, Optional[str]]: - """Separa la parte de fecha de la parte de hora si está presente. - - Devuelve `(date_part, time_part_normalized)`. `time_part_normalized` es - None si no hay hora. La hora se normaliza a `HH:MM[:SS][ AM|PM]`. - """ - if value is None: - return "", None - s = str(value).strip() - if not s: - return "", None - - m = _TIME_TAIL_RE.search(s) - if not m: - return s, None - - hh = int(m.group(1)) - mm = int(m.group(2)) if m.group(2) else 0 - ss = m.group(3) - ampm = m.group(4) - if not (0 <= hh <= 23 and 0 <= mm <= 59): - return s, None - if ss is not None: - try: - ssi = int(ss) - if not (0 <= ssi <= 59): - return s, None - except ValueError: - return s, None - # Validar contra AM/PM (con AM/PM, hora 1-12) - if ampm and not (1 <= hh <= 12): - return s, None - - # Quitar separadores habituales entre fecha y hora (p. ej. "12/27/24, 12:00 AM"). - date_part = s[: m.start()].rstrip(" \t,;T") - if not date_part: - return s, None - - parts: list[str] = [f"{hh:02d}:{mm:02d}"] - if ss is not None: - parts[0] += f":{int(ss):02d}" - if ampm: - norm_ampm = ampm.replace(".", "").replace(" ", "").upper() - if norm_ampm in ("AM", "PM"): - parts.append(norm_ampm) - time_norm = " ".join(parts) - return date_part, time_norm - - -def _extract_date_parts(value: str) -> Optional[tuple[int, int, int, str]]: - """Intenta extraer (a, b, c, layout) de una fecha en cualquier formato común. - - `layout` es uno de: 'sep' (con separadores), 'compact8' (YYYYMMDD o - DDMMYYYY), 'compact6' (DDMMYY o YYMMDD). No interpreta todavía qué - componente es día/mes/año; eso lo decide `detect_date_format` viendo la - columna completa. Acepta valores con hora al final (ignorada para los - componentes de fecha). - """ - if value is None: - return None - s = str(value).strip() - if not s or s.lower() in ("nan", "none", "nat"): - return None - - date_part, _ = _split_date_time(s) - if date_part: - s = date_part - - m = _DATE_NUM_RE.match(s) - if m: - try: - a, b, c = int(m.group(1)), int(m.group(2)), int(m.group(3)) - return (a, b, c, "sep") - except ValueError: - return None - - m2 = _DATE_COMPACT_RE.match(s) - if m2: - digits = m2.group(1) - if len(digits) == 8: - return (int(digits[0:2]), int(digits[2:4]), int(digits[4:8]), "compact8") - if len(digits) == 6: - return (int(digits[0:2]), int(digits[2:4]), int(digits[4:6]), "compact6") - - return None - - -def detect_date_format(series: pd.Series, sample_size: int = 500) -> Optional[str]: - """Analiza la serie y determina el formato de fecha dominante. - - Devuelve un string con el layout detectado (p. ej. "DD/MM/YYYY", - "MM/DD/YYYY", "YYYYMMDD", "DDMMYYYY") o None si no parece fechas. - """ - if series is None or len(series) == 0: - return None - - non_null = series.dropna() - if non_null.empty: - return None - - if len(non_null) > sample_size: - non_null = non_null.sample(sample_size, random_state=42) - - layouts = {"sep": 0, "compact8": 0, "compact6": 0} - sep_first_gt12 = 0 # primer componente > 12 → probable día - sep_second_gt12 = 0 # segundo componente > 12 → probable mes solo posible si MM/DD - sep_third_4digits = 0 # tercer componente de 4 dígitos → año al final - sep_first_4digits = 0 # primer componente de 4 dígitos → año al inicio - compact8_year_first = 0 # primeros 4 dígitos > 1900 → YYYYMMDD - compact8_year_last = 0 # últimos 4 dígitos > 1900 → DDMMYYYY - total_parsed = 0 - - for val in non_null: - parsed = _extract_date_parts(val) - if parsed is None: - continue - total_parsed += 1 - a, b, c, layout = parsed - layouts[layout] = layouts.get(layout, 0) + 1 - - if layout == "sep": - # año puede estar al inicio o al final según cantidad de dígitos - if _digit_count(a) == 4: - sep_first_4digits += 1 - if _digit_count(c) == 4: - sep_third_4digits += 1 - if a > 12: - sep_first_gt12 += 1 - if b > 12: - sep_second_gt12 += 1 - elif layout == "compact8": - # a son los primeros 2 dígitos, c son los últimos 4 - if c >= 1900: - compact8_year_last += 1 - # a concatenado con b son los primeros 4 dígitos - first4 = a * 100 + b - if first4 >= 1900: - compact8_year_first += 1 - - if total_parsed == 0: - return None - - # Requerimos que >=70% de los no nulos parseen como fecha para considerarla - # columna de fechas. - if total_parsed / len(non_null) < 0.7: - return None - - dominant = max(layouts, key=lambda k: layouts[k]) - - if dominant == "compact8": - if compact8_year_first > compact8_year_last: - return "YYYYMMDD" - return "DDMMYYYY" - if dominant == "compact6": - return "DDMMYY" - - # Caso con separadores - if sep_first_4digits > sep_third_4digits: - return "YYYY/MM/DD" - # año al final - if sep_first_gt12 > 0 and sep_second_gt12 == 0: - return "DD/MM/YYYY" - if sep_second_gt12 > 0 and sep_first_gt12 == 0: - return "MM/DD/YYYY" - # ambiguo → preferencia hispana - return "DD/MM/YYYY" - - -def _digit_count(n: int) -> int: - return len(str(abs(n))) - - -def _parse_date_with_format(value: str, fmt: str) -> Optional[tuple[int, int, int]]: - """Convierte un valor de fecha a (día, mes, año) según el layout detectado.""" - parsed = _extract_date_parts(value) - if parsed is None: - return None - a, b, c, layout = parsed - - if fmt == "DD/MM/YYYY": - d, m, y = a, b, c - elif fmt == "MM/DD/YYYY": - m, d, y = a, b, c - elif fmt == "YYYY/MM/DD": - y, m, d = a, b, c - elif fmt == "YYYYMMDD" and layout == "compact8": - # reconstruir: primer4 = a*100 + b, luego mes y día - y = a * 100 + b - m = c // 100 - d = c % 100 - elif fmt == "DDMMYYYY" and layout == "compact8": - d = a - m = b - y = c - elif fmt == "DDMMYY" and layout == "compact6": - d = a - m = b - y = 2000 + c if c < 50 else 1900 + c - else: - # intentar como sep genérico - d, m, y = a, b, c - - if not (1 <= m <= 12 and 1 <= d <= 31 and 1000 <= y <= 9999): - # año en 2 dígitos - if y < 100: - y = 2000 + y if y < 50 else 1900 + y - if not (1 <= m <= 12 and 1 <= d <= 31 and 1000 <= y <= 9999): - return None - return (d, m, y) - - -def _normalize_date_text(value: Any) -> str: - text = unicodedata.normalize("NFKD", str(value or "")) - text = "".join(c for c in text if not unicodedata.combining(c)) - return text.lower() - - -def _valid_date_tuple(d: int, m: int, y: int) -> Optional[tuple[int, int, int]]: - if y < 100: - y = 2000 + y if y < 50 else 1900 + y - if not (1000 <= y <= 9999): - return None - try: - date(y, m, d) - except ValueError: - return None - return (d, m, y) - - -def _split_glued_year_suffix_hhmm(digits: str) -> Optional[tuple[int, int, int]]: - """Detecta `YYYY` + `HHMM` en una sola tira de 8 cifras (p. ej. `20241344`). - - Tras quitar separadores puede quedar pegado al mes (p. ej. `15may20241344`). - Devuelve (año, hora, minuto) si la parte horaria es válida; si no, None. - """ - if len(digits) != 8 or not digits.isdigit(): - return None - y = int(digits[:4]) - hh = int(digits[4:6]) - mm = int(digits[6:8]) - if not (1000 <= y <= 9999 and 0 <= hh <= 23 and 0 <= mm <= 59): - return None - return (y, hh, mm) - - -def _parse_text_month_candidate( - first: tuple[int, str], - month: int, - second: tuple[int, str], - *, - layout: Literal["around", "after", "before"], -) -> Optional[tuple[int, int, int]]: - a, astr = first - b, bstr = second - if layout == "around": - if len(astr) == 4: - return _valid_date_tuple(b, month, a) - if len(bstr) == 4: - return _valid_date_tuple(a, month, b) - return _valid_date_tuple(a, month, b) - if layout == "after": - if len(astr) == 4: - return _valid_date_tuple(b, month, a) - return _valid_date_tuple(a, month, b) - if len(astr) == 4: - return _valid_date_tuple(b, month, a) - return _valid_date_tuple(a, month, b) - - -def _parse_date_with_text_month(raw: Any) -> Optional[tuple[int, int, int]]: - if _is_nullish_scalar(raw): - return None - date_part, _ = _split_date_time(str(raw)) - normalized = _normalize_date_text(date_part or raw) - # `\d{8}` antes que `\d{1,4}` para no partir año+hora pegados tras strip agresivo. - tokens = re.findall(r"[a-z]+|\d{8}|\d{1,4}", normalized) - if not tokens: - return None - - for i, token in enumerate(tokens): - month = _MONTH_NAME_TO_NUM.get(token) - if month is None: - continue - nums_before = [ - (int(t), t) for t in tokens[:i] if t.isdigit() - ] - nums_after = [ - (int(t), t) for t in tokens[i + 1 :] if t.isdigit() - ] - candidates: list[Optional[tuple[int, int, int]]] = [] - if nums_before and len(nums_after) == 1: - glued = _split_glued_year_suffix_hhmm(nums_after[0][1]) - if glued is not None: - y, _hh, _mm = glued - candidates.append( - _parse_text_month_candidate( - nums_before[-1], month, (y, str(y)), layout="around" - ) - ) - if nums_before and nums_after: - candidates.append( - _parse_text_month_candidate( - nums_before[-1], month, nums_after[0], layout="around" - ) - ) - if len(nums_after) >= 2: - candidates.append( - _parse_text_month_candidate( - nums_after[0], month, nums_after[1], layout="after" - ) - ) - if len(nums_before) >= 2: - candidates.append( - _parse_text_month_candidate( - nums_before[-2], month, nums_before[-1], layout="before" - ) - ) - for parsed in candidates: - if parsed is not None: - return parsed - return None - - -# --------------------------------------------------------------------------- -# ANÁLISIS DE COLUMNAS -# --------------------------------------------------------------------------- - -_BOOLEAN_VALUES = {"true", "false", "si", "sí", "no", "1", "0", "yes"} - - -# PARSING numérico tolerante (miles/decimal) sin fijar un solo locale -_HINT_MAJORITY = 0.6 -_MIN_HINT_VOTES = 1 - - -def _is_nullish_scalar(x: Any) -> bool: - if x is None or x is pd.NA: - return True - if isinstance(x, (bool, np.bool_)): - return False - if isinstance(x, (int, float, np.integer, np.floating)) and not isinstance( - x, (bool, np.bool_) - ): - if isinstance(x, float) and (np.isnan(x) or pd.isna(x)): - return True - return False - t = str(x).strip().lower() - if not t or t in ("nan", "none", "nat", "null", ""): - return True - return False - - -def _strip_currency(s: str) -> str: - t = s.replace("\u00a0", " ").replace("\u202f", " ") - t = re.sub(r"[\s]+", "", t) - t = t.strip() - if not t or t.lower() in ("nan", "none", "nat"): - return "" - neg = False - if t[0:1] == "-": - neg, t = not neg, t[1:].lstrip() - if t[0:1] == "(" and t.endswith(")"): - neg, t = not neg, t[1:-1] - t = re.sub( - r"^[\$€\u00a3\u0e3f\uffe5\uffe6\uff04\ufe69]+", - "", - t, - count=1, - ) - t = re.sub(r"[\$€%]+$", "", t) - if not t and neg: - return "-0" - return ("-" if neg else "") + t - - -def _extract_numeric_chunk(s: str) -> str: - t = s.strip() - t = t.replace("$", "").replace("€", "").replace("\u00a3", "") - t = t.replace(" ", "") - cands1 = re.findall( - r"[+\-]?(?:\d{1,3}(?:[.,]\d{3})*|\d+)(?:[.,]\d+)?", t - ) - cands2 = re.findall( - r"[+\-]?\d[.,\d ]*\d", t - ) - cands = cands1 + cands2 - if not cands: - m = re.search(r"[\d.,\-]+", t) - if m: - c = m.group(0).replace("(", "-").replace(")", "") - return c.lstrip("+") - return "" - best = max(cands, key=len) if cands else "" - best = best.replace(" ", "").replace("(", "-").replace(")", "") - if not re.search(r"\d", best): - return "" - return best.lstrip("+") - - -def _as_float_s(s: str) -> Optional[float]: - if not s: - return None - try: - f = float(s) - if not np.isfinite(f) or abs(f) > 1e100: - return None - return f - except (ValueError, TypeError, OverflowError): - return None - - -def _parse_both_separators(body: str) -> Optional[float]: - """El separador más a la DERECHA entre `,` y `.` es el decimal; el otro, miles.""" - if "," not in body or "." not in body: - return None - t = body.strip() - if re.search(r"[^0-9,.\-]", t): - t2 = re.sub(r"[^0-9,.\-]", "", t) - t = t2 - if not t: - return None - neg = t[0:1] == "-" - b = t[1:].lstrip() if neg else t - if not b: - return None - i_comma, i_dot = b.rfind(","), b.rfind(".") - if min(i_comma, i_dot) < 0: - return None - dpos = max(i_comma, i_dot) - th = "," if b[dpos] == "." else "." - intp, fr = b[:dpos], b[dpos + 1 :] - if not re.fullmatch(r"\d{1,4}$", fr): - return None - intc = intp.replace(th, "") - if not intc.isdigit(): - return None - v = _as_float_s(f"{intc}.{fr}") - if v is None: - return None - if neg and v and v is not None: - return -abs(v) - return v - - -def _only_commas(w: str, dec_hint: Optional[Literal[",", "."]]) -> Optional[float]: - neg = w[0:1] == "-" - wn = w[1:].lstrip() if neg else w - if wn and re.search(r"[^0-9,]", wn): - wn = re.sub(r"[^0-9,]", "", wn) - n = wn.count(",") - if n == 0 and wn.isdigit(): - v = float(wn) - return -v if neg else v - if n == 0 or "." in wn: - return None - if n == 1: - i = wn.rfind(",") - h, t = wn[:i], wn[i + 1 :] - if not t.isdigit() or not h.isdigit(): - return None - if len(t) in (1, 2): - v = _as_float_s(f"{h}.{t}") - if v is None: - return None - return -v if neg else v - if len(t) == 3: - if dec_hint is None or dec_hint == ",": - v = _as_float_s(f"{h}{t}") - if v is None: - return None - return -v if neg else v - if dec_hint == ".": - v = _as_float_s(wn.replace(",", "")) - if v is None: - return None - return -v if neg else v - v = _as_float_s(wn.replace(",", "")) - if v is None: - return None - return -v if neg else v - - -def _only_dots(w: str, dec_hint: Optional[Literal[",", "."]]) -> Optional[float]: - neg = w[0:1] == "-" - wn = w[1:].lstrip() if neg else w - if wn and re.search(r"[^0-9.]", wn): - wn = re.sub(r"[^0-9.]", "", wn) - n = wn.count(".") - if n == 0 and wn.isdigit(): - v = float(wn) - return -v if neg else v - if n == 0 or "," in wn: - return None - if n == 1: - i = wn.rfind(".") - h, t = wn[:i], wn[i + 1 :] - if not t.isdigit() or not h.isdigit(): - return None - if len(t) in (1, 2): - v = _as_float_s(f"{h}.{t}") - if v is None: - return None - return -v if neg else v - if len(t) == 3: - if dec_hint is None or dec_hint == ".": - v = _as_float_s(f"{h}{t}") - if v is None: - return None - return -v if neg else v - if dec_hint == ",": - v = _as_float_s(wn.replace(".", "")) - if v is None: - return None - return -v if neg else v - v = _as_float_s(wn.replace(".", "")) - if v is None: - return None - return -v if neg else v - - -def _parse_lenient_one( - raw: Any, - decimal_hint: Optional[Literal[",", "."]] = None, -) -> float: - if _is_nullish_scalar(raw): - return float("nan") - t = _strip_currency(str(raw)) - if not t or t == "-": - return float("nan") - c = _extract_numeric_chunk(t) or t - c = c.strip() - if not c or c == "-": - return float("nan") - n_comma, n_dot = c.count(","), c.count(".") - if n_comma and n_dot: - f = _parse_both_separators(c) - elif n_comma and not n_dot: - f = _only_commas(c, decimal_hint) - elif n_dot and not n_comma: - f = _only_dots(c, decimal_hint) - else: - c2 = re.sub(r"[^0-9.\-]", "", c) - f = _as_float_s(c2) if c2 and re.fullmatch(r"-?\d+$", c2) else None - if f is not None and np.isfinite(f): - return f - return float("nan") - - -def _infer_decimal_hint_from_column( - series: pd.Series, -) -> Optional[Literal[",", "."]]: - """Voto con celdas que traen `,` y `.`: gana el de más a la derecha = decimal.""" - votes: list[Literal[",", "."]] = [] - for raw in series.dropna(): - if _is_nullish_scalar(raw): - continue - t = _strip_currency(str(raw)) - c = _extract_numeric_chunk(t) or t - c = c.strip() - if "," not in c or "." not in c or _parse_both_separators(c) is None: - continue - if c.rfind(",") > c.rfind("."): - votes.append(",") - else: - votes.append(".") - n = len(votes) - if n < _MIN_HINT_VOTES: - return None - a = sum(1 for v in votes if v == ",") - b = sum(1 for v in votes if v == ".") - if a + b == 0: - return None - if a / n >= _HINT_MAJORITY and a > b: - return "," - if b / n >= _HINT_MAJORITY and b > a: - return "." - return None - - -def _to_numeric_lenient( - series: pd.Series, - decimal_hint: Optional[Literal[",", "."]] = None, -) -> pd.Series: - """Convierte a flotante; pista de columna por voto (si `decimal_hint` es None).""" - hint = decimal_hint if decimal_hint is not None else _infer_decimal_hint_from_column( - series - ) - - def _cell(x: Any) -> float: - if _is_nullish_scalar(x): - return float("nan") - return _parse_lenient_one(x, hint) - - return series.map(_cell) - - -def _column_name_suggests_date_like(name: str) -> bool: - """Columnas cuyo nombre pide tratar el contenido como fecha a priori.""" - n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() - if not n: - return False - if re.search( - r"\b(fecha|date|año|mes|dia|día|f[_-]?(alta|baja|proc|corte|pago|ini|fin|" - r"desde|hasta|vigenc|vence|venc|caduc|f\s?ec)\b|periodo|anio|ano|expir)", - n, - ) or n.startswith("fec_") or n.startswith("fecha"): - return True - return False - - -def _column_name_suggests_monetary_value(name: str) -> bool: - """importe, valor, neto, IVA, etc. — no interpretar cifras como fechas. - - Encabezados de tipo `fecha_…` no se tratan como moneda salvo mezcla con monto/valor. - """ - n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() - if not n: - return False - if _column_name_suggests_date_like(name) and not re.search( - r"\b(monto|valor|importe|pago|total|bruto|neto|import|unit|precio|copago)\b", n - ): - return False - for key in ( - "monto", "importe", "import", "bruto", "neto", "subtotal", "sub total", - "unitario", "unidad", "valor", "precio", "pago", "copago", "sald", "dcto", - "franq", "cuota", "iva", "tarif", "costo", "carg", "reten", "retencion", - ): - if key in n: - if key == "iva" and re.search(r"(priva|giva|diva|viva|cliva)\b", n): - continue - if key == "carg" and re.search(r"descarg|recarg|abord", n): - continue - return True - return False - - -def _column_name_suggests_code_id(name: str) -> bool: - """Código, ref., radicado, tipoynúm de documento — no forzar 6-8 cifras como fechas (DDMMYY, etc.).""" - n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() - if not n or _column_name_suggests_date_like(name): - return False - for key in ( - "codigo", "código", "codig", "códig", "cód_", - "sku", "ean", "upc", "isbn", "upn", - "ref", "referen", "radic", "nro_fac", "nro fac", "nit", "cédul", "cedul", - "autoriz", "correlat", "secuenc", "línea de", "linea de", "cupo", - "tipy", "tipi", "tipynum", "typnum", "type_n", "n_doc", "n_docu", - "núm doc", "n_docu", "doc_id", "docum", "id_doc", - ): - if key in n: - return True - if re.search( - r"tipo[_\s\\-]*(&|/|u|e|y)[_\s\\-]*num|tipo[_\s\\-]+doc|tipo_y_n", - n, - re.IGNORECASE, - ) or n.startswith("tip_"): - return True - if n.startswith("cod_") or n.startswith("cód_") or n.startswith("ref_"): - return True - return bool(re.search(r"\b(nro[._]fac|#?\s*autor|#?\s*ref)\b", n, re.IGNORECASE)) - - -def _column_name_suggests_quantity_or_number_not_bool(name: str) -> bool: - """Nombres típicos de cantidad, importe, fechas: no inferir bool por 0/1.""" - n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() - if not n: - return False - if _column_name_suggests_monetary_value(name) or _column_name_suggests_code_id(name): - return True - for key in ( - "fecha", - "monto", - "importe", - "bruto", - "neto", - "subtotal", - "unitario", - "unidad", - "valor", - "precio", - "pago", - "copago", - "cantid", - "quantity", - "qty", - "nro", - "núm", - "n°", - ): - if key in n: - return True - return False - - -def _raw_strings_suggest_monetary_presentation(non_null: pd.Series) -> bool: - """$ €, miles 1,234,12 o .00: presentación monetaria aunque el float sea "entero".""" - if non_null is None or len(non_null) == 0: - return False - n_hit = 0 - n_seen = 0 - for raw in non_null.head(500): - if _is_nullish_scalar(raw): - continue - t = str(raw) - s = t.strip() - n_seen += 1 - if re.search(r"[$\u00a3\u20ac\uffe5\uffe6\ufe69\uff04]", t): - n_hit += 1 - continue - if re.search(r"\b\d{1,3}(?:[.,\u00a0]\d{3})+\b", s) or re.search( - r"(?= 0.10 - - -def _cell_looks_cifra_con_moneda_iso_al_final(s: str) -> bool: - """Solo cifra/símbolo (no prefijo de documento) y código de moneda al final. - - Excluye filas al estilo `CC12…` (típico alfanumérico). - """ - t = str(s).strip() - u = t.upper() - if not re.search( - r"(?i)(USD|COP|EUR|CLP|ARS|GBP|MXN|PEN|DOP|UYU|VES|CRC|PYG|BRL|BOB|GTQ)\b\s*\Z", u - ): - return False - if re.match( - r"^(?i)(CC|CE|NIT|NUIP|TI|PA|MS|RC|DNI|NUIP|CN|SC|AS|PE|PT)\D*[\d0]", u - ): - return False - return re.match( - r"^[\$€0-9\s,.\-+%/]", t, - ) is not None - - -def _raw_strings_suggest_alnum_mixed_presentation(non_null: pd.Series) -> bool: - """Letras y dígitos juntos (p. ej. `CC32…`, `A1-2B`); no es puro 'entero/decimal' lógicamente. - - Excluido: columnas con apariencia monetaria, y celdas solo cifra + ISO al final (100 USD). - """ - if _raw_strings_suggest_monetary_presentation(non_null): - return False - n_m = 0 - n_s = 0 - for raw in non_null.head(500): - if _is_nullish_scalar(raw): - continue - t = str(raw).strip() - n_s += 1 - if not re.search(r"\d", t) or not re.search( - r"[A-Za-zÑñ\u00c0-\u024f]", t, re.IGNORECASE - ): - continue - if _cell_looks_cifra_con_moneda_iso_al_final(t): - continue - n_m += 1 - if n_s == 0: - return False - return n_m / n_s >= 0.20 - - -def _cell_looks_like_documento_id_tipo_nit(s: str) -> bool: - """Cadenas estilo `CC32324029`, `CE 1.234.567` — no son fechas (evita 8 cifras como DDMM). - - Al menos una letra al inicio y 5-16 dígitos en la celda (no `Jan`+`2020` = 4 dígitos). - """ - t = str(s).strip() - if not t or len(t) < 3: - return False - if not re.match(r"^[A-Za-z]{1,3}", t): - return False - dig = re.sub(r"[^\d]", "", t) - if len(dig) < 5 or len(dig) > 16: - return False - return True - - -def _content_looks_like_doc_id_letter_prefix_column(non_null: pd.Series) -> bool: - """Muestras con prefijo documental (CC, NIT, …) y ≥5 dígitos → no columna de fecha.""" - n_ok = 0 - n_seen = 0 - for raw in non_null.head(500): - if _is_nullish_scalar(raw): - continue - s = str(raw).strip() - n_seen += 1 - if _cell_looks_like_documento_id_tipo_nit(s): - n_ok += 1 - if n_seen == 0: - return False - return n_ok / n_seen >= 0.20 - - -def _detect_messy_text_date_format_guess(non_null: pd.Series) -> Optional[str]: - """Fechas con ruido: compacta a dígitos; si hay consenso, reutiliza el detector. - (Depende de `_normalize_messy_date_to_compact`, definida más abajo en el módulo.) - """ - # resolución en tiempo de ejecución - norm = non_null.map(_normalize_messy_date_to_compact) # type: ignore[name-defined] - compacts: list[str] = [] - for c in norm: - if isinstance(c, str) and len(c) in (6, 8): - compacts.append(c) - n = max(len(non_null), 1) - if not compacts or len(compacts) < max(2, int(0.25 * n)): - return None - s_compact = pd.Series(compacts, dtype=object) - return detect_date_format(s_compact) - - -def _infer_column_type( - series: pd.Series, column_name: str = "" -) -> tuple[str, Optional[str]]: - """Devuelve (tipo, formato_fecha detectado; solo si aplica a tipo fecha).""" - non_null = series.dropna().astype(str).str.strip() - non_null = non_null[non_null != ""] - if non_null.empty: - return TYPE_EMPTY, None - - cname = str(column_name or "") - - # Boolean: no aplicar 0/1/ sí-no a columnas cuyo nombre pide importe, cantid., fecha… - if not _column_name_suggests_quantity_or_number_not_bool(cname): - lowered = non_null.str.lower() - if lowered.isin(_BOOLEAN_VALUES).all() and lowered.nunique() <= 3: - return TYPE_BOOLEAN, None - - is_money = _column_name_suggests_monetary_value(cname) - is_code = _column_name_suggests_code_id(cname) - is_doc_pattern = _content_looks_like_doc_id_letter_prefix_column(non_null) - - # Fecha: no forzar en importe/código, ni con datos tipo `CC`+cifra (6–8 dígitos sueltos) - if not is_money and not is_code and not is_doc_pattern: - date_fmt = detect_date_format(non_null) - if date_fmt: - return (TYPE_DATE, date_fmt) - messy_date_fmt = _detect_messy_text_date_format_guess(non_null) - if messy_date_fmt: - return (TYPE_DATE, messy_date_fmt) - - numeric_candidate = _to_numeric_lenient(non_null, decimal_hint=None) - numeric_ratio = numeric_candidate.notna().mean() - if numeric_ratio >= 0.9: - if _raw_strings_suggest_alnum_mixed_presentation(non_null): - return (TYPE_MIXED, None) - is_all_integer = (numeric_candidate.dropna() % 1 == 0).all() - if is_all_integer and not _raw_strings_suggest_monetary_presentation(non_null): - return (TYPE_INTEGER, None) - return (TYPE_DECIMAL, None) - - unique_ratio = non_null.nunique() / len(non_null) - if non_null.nunique() <= 15 and unique_ratio < 0.3: - return TYPE_CATEGORICAL, None - if 0.1 < numeric_ratio < 0.9: - return TYPE_MIXED, None - return TYPE_TEXT, None - - -def _applicable_rules_for_type(inferred_type: str) -> list[str]: - """Identificadores de reglas a mostrar según el tipo inferido (sin redundancias).""" - base = ["strip_special_spaces", "strip_special_keep_date_seps", "strip_spaces"] - if inferred_type == TYPE_INTEGER: - return base + ["cop_mod_split"] - if inferred_type == TYPE_DECIMAL: - return base + [ - "to_integer", - "round_decimals_custom", - "doc_id_split_tipo_numero", - "doc_type_homologate", - "cop_mod_split", - ] - if inferred_type == TYPE_DATE: - return base + [ - "doc_id_split_tipo_numero", - "doc_type_homologate", - "date_to_ddmmyyyy", - "nota_paciente_autorizacion", - ] - if inferred_type == TYPE_MIXED: - return base + [ - "to_integer", - "round_decimals_custom", - "doc_id_split_tipo_numero", - "doc_type_homologate", - "date_to_ddmmyyyy", - "cop_mod_split", - "nota_paciente_autorizacion", - ] - # Texto / categórico / boolean / empty → todas las de limpieza de texto + las - # conversoras (el usuario sabrá si aplica). - return base + [ - "to_integer", - "round_decimals_custom", - "doc_id_split_tipo_numero", - "doc_type_homologate", - "date_to_ddmmyyyy", - "cop_mod_split", - "nota_paciente_autorizacion", - ] - - -applicable_rule_ids_for_type = _applicable_rules_for_type - - -def analyze_dataframe(df: pd.DataFrame) -> list[ColumnSummary]: - """Genera un resumen por columna del DataFrame.""" - summaries: list[ColumnSummary] = [] - total_rows = len(df) - rng = random.Random(42) - - for col in df.columns: - series = df[col] - null_count = int(series.isna().sum() + (series.astype(str).str.strip() == "").sum()) - # Evitar doble conteo: considerar vacío solo si no-NaN - null_count = int((series.isna() | (series.astype(str).str.strip() == "")).sum()) - null_pct = (null_count / total_rows * 100) if total_rows else 0.0 - - non_null = series.dropna().astype(str) - non_null = non_null[non_null.str.strip() != ""] - - example = "" - if not non_null.empty: - example = str(rng.choice(non_null.tolist())) - if len(example) > 80: - example = example[:77] + "..." - - unique_count = int(non_null.nunique()) - unique_pct = (unique_count / total_rows * 100) if total_rows else 0.0 - - inferred_type, date_fmt = _infer_column_type(series, str(col)) - - summary = ColumnSummary( - name=str(col), - inferred_type=inferred_type, - example=example, - null_count=null_count, - null_pct=null_pct, - unique_count=unique_count, - unique_pct=unique_pct, - detected_date_format=date_fmt, - ) - - if inferred_type in (TYPE_INTEGER, TYPE_DECIMAL): - numeric = _to_numeric_lenient(non_null) - if numeric.notna().any(): - summary.min = float(numeric.min()) - summary.max = float(numeric.max()) - summary.mean = float(numeric.mean()) - elif inferred_type in (TYPE_TEXT, TYPE_CATEGORICAL): - if not non_null.empty: - summary.avg_length = float(non_null.str.len().mean()) - counts = non_null.value_counts().head(3) - summary.top_values = [(str(v), int(c)) for v, c in counts.items()] - - summary.applicable_rules = _applicable_rules_for_type(inferred_type) - summaries.append(summary) - - return summaries - - -# --------------------------------------------------------------------------- -# REGLAS DE LIMPIEZA -# --------------------------------------------------------------------------- - - -def _rule_strip_special_spaces(series: pd.Series) -> pd.Series: - """Elimina todo lo que no sea alfanumérico (incluye espacios).""" - pattern = re.compile(r"[^A-Za-z0-9]", flags=re.UNICODE) - - def _fn(x): - if pd.isna(x): - return x - return pattern.sub("", str(x)) - - return series.map(_fn) - - -def _rule_strip_special_keep_date_seps(series: pd.Series) -> pd.Series: - """Conserva alfanuméricos, separadores de fecha '/', '-' y hora ':', y espacio.""" - pattern = re.compile(r"[^A-Za-z0-9\/\-: ]", flags=re.UNICODE) - - def _fn(x): - if pd.isna(x): - return x - return pattern.sub("", str(x)) - - return series.map(_fn) - - -def _rule_strip_spaces(series: pd.Series) -> pd.Series: - """Elimina únicamente espacios (incluidos tabs y saltos de línea).""" - def _fn(x): - if pd.isna(x): - return x - return re.sub(r"\s+", "", str(x)) - - return series.map(_fn) - - -def _rule_to_integer(series: pd.Series) -> pd.Series: - """Convierte a entero (Int64 nullable) con parser tolerante.""" - numeric = _to_numeric_lenient(series.astype(str)) - return numeric.round().astype("Int64") - - -def _round_to_int_half_tie_le(x: float) -> int: - """Fracc. > 0,5 hacia arriba; 0,5 o menos hacia abajo. Banda 1e-9 en 0,5 (float). - - Misma noción que en la descripción de la regla. Trabaja sobre |x| y - recompone el signo. - """ - sign = -1 if x < 0 else 1 - if x == 0.0 or x == -0.0: - return 0 - ax = abs(float(x)) - ip = math.trunc(ax) - frac = ax - ip - if frac < 0.5 - 1e-9: - out = ip - elif frac > 0.5 + 1e-9: - out = ip + 1 - else: - out = ip - return -out if sign < 0 else out - - -def _rule_round_decimals_custom(series: pd.Series) -> pd.Series: - """Mismo parser tolerante que convertir a entero; aprox. con fracc. >0,5 arriba.""" - numeric = _to_numeric_lenient(series.astype(str)) - - def _fn(x): - if _is_nullish_scalar(x) or (isinstance(x, (float, np.floating)) and (not np.isfinite(x) or np.isnan(x))): - return pd.NA - try: - f = float(x) - except (TypeError, ValueError, OverflowError): - return pd.NA - if not np.isfinite(f): - return pd.NA - if f == 0.0 or f == -0.0: - return 0 - return _round_to_int_half_tie_le(f) - - return numeric.map(_fn).astype("Int64") - - -def _rule_doc_type_homologate(series: pd.Series) -> pd.Series: - """Homologa una columna que contiene tipos de documento.""" - def _fn(x: Any): - if _is_nullish_scalar(x): - return x - tipo, _num = split_doc_tipo_y_numero_texto(x, homologate=True) - if tipo is not None: - return tipo - return homologate_doc_type(x) - - return series.map(_fn) - - -def _detect_compact_date_formats( - series: pd.Series, -) -> tuple[Optional[str], Optional[str]]: - cser = series.map(_normalize_messy_date_to_compact) - c8s = pd.Series( - [x for x in cser.tolist() if isinstance(x, str) and len(x) == 8] - ) - c6s = pd.Series( - [x for x in cser.tolist() if isinstance(x, str) and len(x) == 6] - ) - fmt8 = detect_date_format(c8s) if not c8s.empty else None - fmt6 = detect_date_format(c6s) if not c6s.empty else None - return fmt8, fmt6 - - -def _parse_any_date_to_tuple( - raw: Any, - *, - sep_fmt: Optional[str], - compact8_fmt: Optional[str], - compact6_fmt: Optional[str], -) -> Optional[tuple[int, int, int]]: - parsed_text = _parse_date_with_text_month(raw) - if parsed_text is not None: - return parsed_text - - if _is_nullish_scalar(raw): - return None - - raw_text = str(raw) - date_part, _ = _split_date_time(raw_text) - target = date_part if date_part else raw_text - if sep_fmt: - parsed = _parse_date_with_format(target, sep_fmt) - if parsed is not None: - return parsed - for fallback_fmt in ("DD/MM/YYYY", "MM/DD/YYYY", "YYYY/MM/DD"): - if fallback_fmt == sep_fmt: - continue - parsed = _parse_date_with_format(target, fallback_fmt) - if parsed is not None: - return parsed - - compact = _normalize_messy_date_to_compact(raw) - if not isinstance(compact, str): - return None - if len(compact) == 8: - for fmt in (compact8_fmt, "DDMMYYYY", "YYYYMMDD"): - if not fmt: - continue - parsed = _parse_date_with_format(compact, fmt) - if parsed is not None: - return parsed - if len(compact) == 6: - parsed = _parse_date_with_format(compact, compact6_fmt or "DDMMYY") - if parsed is not None: - return parsed - return None - - -def _rule_date_to_ddmmyyyy(series: pd.Series) -> pd.Series: - """Normaliza fechas numéricas, alfanuméricas, con meses en texto y con ruido.""" - stripped = series.astype(str).str.replace(r"[\s\/\-\.]", "", regex=True) - stripped = stripped.where(series.notna(), other=pd.NA) - sep_fmt = detect_date_format(series) or detect_date_format(stripped) - compact8_fmt, compact6_fmt = _detect_compact_date_formats(series) - - def _fn(x: Any): - if pd.isna(x) or str(x).strip() == "": - return x - parsed = _parse_any_date_to_tuple( - x, - sep_fmt=sep_fmt, - compact8_fmt=compact8_fmt, - compact6_fmt=compact6_fmt, - ) - if parsed is None: - return x - d, m, y = parsed - return f"{d:02d}/{m:02d}/{y:04d}" - - return series.map(_fn) - - -def _rule_date_numeric_to_ddmmyyyy(series: pd.Series) -> pd.Series: - """Entrada: fechas en formato numérico (ej. 20240315 o 15032024). - - Salida: "DD/MM/AAAA" tras detectar el formato predominante. - """ - # Para esta regla consideramos los valores "numéricos" (quitamos separadores - # comunes si los hay para detectar un YYYYMMDD/DDMMYYYY confiable). - stripped = series.astype(str).str.replace(r"[\s\/\-\.]", "", regex=True) - stripped = stripped.where(series.notna(), other=pd.NA) - fmt = detect_date_format(stripped) - if fmt is None: - # fallback: intentar con detector normal - fmt = detect_date_format(series) - return _format_dates_to_ddmmyyyy(series, fmt) - - -def _rule_date_alpha_to_ddmmyyyy(series: pd.Series) -> pd.Series: - """Entrada: fechas en formato alfanumérico con separadores. - - Salida: "DD/MM/AAAA". - """ - fmt = detect_date_format(series) - return _format_dates_to_ddmmyyyy(series, fmt) - - -def _format_dates_to_ddmmyyyy(series: pd.Series, fmt: Optional[str]) -> pd.Series: - """Convierte cada valor al formato DD/MM/AAAA usando `fmt` como layout de - entrada detectado. La parte de hora (si existe) se descarta. Mantiene el - valor original si no puede parsear la fecha. - """ - if fmt is None: - return series.copy() - - def _fn(x): - if pd.isna(x) or str(x).strip() == "": - return x - raw = str(x) - date_part, _time_part = _split_date_time(raw) - target = date_part if date_part else raw - parsed = _parse_date_with_format(target, fmt) - if parsed is None: - return x - d, m, y = parsed - return f"{d:02d}/{m:02d}/{y:04d}" - - return series.map(_fn) - - -# --------------------------------------------------------------------------- -# Fechas con ruido (texto sucio) → compacto numérico + DD/MM/AAAA -# --------------------------------------------------------------------------- - - -def _normalize_messy_date_to_compact(raw: Any) -> Optional[str]: - """Quita ruido (* + _ / espacios, etc.); deja 6 o 8 dígitos o None. - - Soporta separadores entremezclados; hora al final vía `_split_date_time`. - Con 9+ dígitos, heurística: tomar 8 iniciales (p. ej. 9.º = 0 colado) y - validar luego con `_parse_date_with_format`; con más de 8 sobrantes, - 8 iniciales. - """ - if _is_nullish_scalar(raw): - return None - s = str(raw).strip() - if s.lower() in ("nan", "none", "nat", ""): - return None - date_part, _ = _split_date_time(s) - body = date_part if date_part else s - d = re.sub(r"[^\d]", "", body) - if not d: - return None - n = len(d) - if n in (6, 8): - return d - if n < 6 or n == 7: - return None - # 9+ dígitos: 8 iniciales (p. ej. 9.º 0 sobrante, o ruido al final) - return d[:8] - - -def _rule_date_messy_text_to_ddmmyyyy(series: pd.Series) -> pd.Series: - """Fechas con símbolos y basura entre cifras → DD/MM/AAAA. - - Normaliza a solo 6/8 dígitos, vota el formato con `detect_date_format` por - separado en compact8 y compact6; si el detector no devuelve nada, usa - `DDMMYYYY` / `DDMMYY` (alineado con la preferencia hispana en fechas - numéricas). - """ - cser = series.map(_normalize_messy_date_to_compact) - c8s = pd.Series( - [x for x in cser.tolist() if isinstance(x, str) and len(x) == 8] - ) - c6s = pd.Series( - [x for x in cser.tolist() if isinstance(x, str) and len(x) == 6] - ) - if c8s.empty and c6s.empty: - return series.copy() - - fmt8: Optional[str] = None - fmt6: Optional[str] = None - if not c8s.empty: - fmt8 = detect_date_format(c8s) or "DDMMYYYY" - if not c6s.empty: - fmt6 = detect_date_format(c6s) or "DDMMYY" - - def _one(x: Any, c: Any) -> Any: - if _is_nullish_scalar(x): - return x - if not isinstance(c, str) or len(c) not in (6, 8): - return x - f = (fmt8 or "DDMMYYYY") if len(c) == 8 else (fmt6 or "DDMMYY") - t = _parse_date_with_format(c, f) - if t is None and len(c) == 8: - t = _parse_date_with_format(c, "DDMMYYYY") or _parse_date_with_format( - c, "YYYYMMDD" - ) - if t is None and len(c) == 6: - t = _parse_date_with_format(c, "DDMMYY") - if t is None: - return x - d, m, y = t - return f"{d:02d}/{m:02d}/{y:04d}" - - return pd.Series( - [ - _one(v, cser.iloc[i] if i < len(cser) else None) for i, v in enumerate(series) - ], - index=series.index, - dtype=object, - ) - - -# --------------------------------------------------------------------------- -# Tipo y número de documento (CC, NIT, CE, MS, …) → 2 columnas -# --------------------------------------------------------------------------- - -_DOC_TYPE_ALIASES: dict[str, tuple[str, ...]] = { - "CC": ("CC", "Cedula", "CD", "C", "C.C", "C C", "CCC"), - "TI": ("TI", "Tarjeta", "Tarjeta Identidad", "T1", "T", "TL"), - "CN": ("CN", "Certificado", "Nacido Vivo", "Certificado Nacido Vivo"), - "CE": ("CE", "Cedula de Extranjeria", "Extranjera"), - "ME": ("ME", "Menor sin Identificacion"), - "PS": ("PS", "Pasaporte"), - "PT": ("PT", "Permiso por proteccion Temporal"), - "RC": ("RC", "Registro Civil", "Registro", "TC"), - "SC": ("SC", "Salvo Conducto", "Salvoconducto"), - "PE": ("PE", "Permiso especial de permanencia", "Permiso"), -} - - -def _ascii_upper(value: Any) -> str: - text = unicodedata.normalize("NFKD", str(value or "")) - text = "".join(c for c in text if not unicodedata.combining(c)) - return text.upper() - - -def _normalize_doc_type_key(value: Any) -> str: - """Clave comparable: sin acentos, espacios, puntos ni símbolos.""" - return re.sub(r"[^A-Z0-9]", "", _ascii_upper(value)) - - -def _clean_doc_type_text(value: Any) -> Optional[str]: - cleaned = re.sub(r"[^A-Z0-9]+", " ", _ascii_upper(value)).strip() - cleaned = re.sub(r"\s+", " ", cleaned) - return cleaned or None - - -_DOC_TYPE_ALIAS_BY_KEY: dict[str, str] = { - _normalize_doc_type_key(alias): canonical - for canonical, aliases in _DOC_TYPE_ALIASES.items() - for alias in aliases -} - -_DOC_TYPE_ALIAS_DISPLAY_BY_KEY: dict[str, str] = {} -for _canonical, _aliases in _DOC_TYPE_ALIASES.items(): - for _alias in _aliases: - _alias_key = _normalize_doc_type_key(_alias) - _alias_display = _clean_doc_type_text(_alias) - if _alias_key and _alias_display: - _DOC_TYPE_ALIAS_DISPLAY_BY_KEY.setdefault(_alias_key, _alias_display) - -_DOC_TYPE_ALIAS_KEYS_SORTED: list[str] = sorted( - _DOC_TYPE_ALIAS_BY_KEY, - key=lambda k: (len(k), k), - reverse=True, -) - - -def homologate_doc_type(value: Any) -> Optional[str]: - """Homologa un tipo documental a sigla; si no existe, conserva texto limpio.""" - if _is_nullish_scalar(value): - return None - key = _normalize_doc_type_key(value) - if not key or key.lower() in ("nan", "none", "nat"): - return None - return _DOC_TYPE_ALIAS_BY_KEY.get(key) or _clean_doc_type_text(value) - - -def _compact_doc_text(value: Any) -> str: - return _normalize_doc_type_key(value) - - -def _looks_like_short_alias_prefix(compact: str, alias_key: str) -> bool: - """Evita que alias de 1-2 chars capturen palabras como CODIGO o TOTAL.""" - if not compact.startswith(alias_key): - return False - rest = compact[len(alias_key):] - if not rest: - return True - return bool(rest[0].isdigit()) - - -def _match_known_doc_type_prefix(compact: str) -> Optional[tuple[str, str]]: - for alias_key in _DOC_TYPE_ALIAS_KEYS_SORTED: - if len(alias_key) <= 2: - if not _looks_like_short_alias_prefix(compact, alias_key): - continue - elif not compact.startswith(alias_key): - continue - return _DOC_TYPE_ALIAS_BY_KEY[alias_key], alias_key - return None - - -def split_doc_tipo_y_numero_texto( - raw: Any, - *, - homologate: bool = True, -) -> tuple[Optional[str], Optional[str]]: - """Separa tipo documental y número, tolerando nombres largos y símbolos. - - Si `homologate` es True, tipos conocidos se devuelven como sigla (CC, TI, - etc.). Los tipos desconocidos se conservan limpios en mayúsculas. - """ - if _is_nullish_scalar(raw) and not (isinstance(raw, str) and not str(raw).strip()): - return (None, None) - t = str(raw).strip() - if t.lower() in ("nan", "none", "nat", ""): - return (None, None) - if t and t[0].isdigit(): - return (None, None) - - compact = _compact_doc_text(t) - if not compact: - return (None, None) - - match = _match_known_doc_type_prefix(compact) - if match is not None: - canonical, alias_key = match - rest = compact[len(alias_key):] - nums = re.sub(r"[^\d]", "", rest) - if homologate: - return (canonical, nums if nums else None) - raw_type = _DOC_TYPE_ALIAS_DISPLAY_BY_KEY.get(alias_key) or canonical - return (raw_type, nums if nums else None) - - m = re.match( - r"^([^\d]+?)\s*([0-9].*)\s*$", - t, - ) - if m: - to_t = _clean_doc_type_text(m.group(1)) - nums = re.sub(r"[^\d]", "", m.group(2)) - return (homologate_doc_type(to_t) if homologate else to_t, nums if nums else None) - - if not re.search(r"\d", t): - to_t = homologate_doc_type(t) if homologate else _clean_doc_type_text(t) - return (to_t, None) - return (None, None) - - -def _alloc_doc_split_column_names( - col_name: str, existing: set[str] -) -> tuple[str, str]: - """Nombres únicos para las dos columnas derivadas.""" - base = re.sub(r"[^\w]+", "_", str(col_name).strip(), flags=re.ASCII) or "col" - for i in range(0, 200): - suf = f"_{i}" if i else "" - t, n = f"{base}{suf}_doc_tipo", f"{base}{suf}_doc_numeros" - if t not in existing and n not in existing: - return t, n - raise ValueError("No se pudieron asignar nombres de columnas únicos.") - - -def _rule_passthrough_series(s: pd.Series) -> pd.Series: - return s.copy() - - -def _apply_doc_id_split_columns( - result: pd.DataFrame, col: str, log: list[str], *, homologate_type: bool = True -) -> None: - tcol, ncol = _alloc_doc_split_column_names(str(col), set(result.columns)) - st_tipo: list[Optional[str]] = [] - st_num: list[Any] = [] - for v in result[col].tolist(): - tt, nn = split_doc_tipo_y_numero_texto(v, homologate=homologate_type) - st_tipo.append(tt) - st_num.append(nn if nn is not None else pd.NA) - result[tcol] = st_tipo - result[ncol] = st_num - log.append( - f"[OK] '{str(col)}' -> columnas '{tcol}' y '{ncol}' <- Dividir tipo y número (documento)" - ) - - -# --- Texto tipo nota (Paciente / Autorización) -------------------------------- - -_PACIENTE_DOC_NOTA_RE = re.compile(r"(?i)Paciente:\s*(\d+)\s*-\s*") -_AUTORIZACION_NOTA_RE = re.compile(r"(?i)Autorizaci[oó]n\s*No\.?\s*(\d+)") - - -def _normalize_nota_text(raw: Any) -> str: - if _is_nullish_scalar(raw): - return "" - try: - if pd.isna(raw): - return "" - except TypeError: - pass - s = str(raw).strip() - if not s or s.lower() in ("nan", "none", "nat", ""): - return "" - return re.sub(r"\s+", " ", s) - - -def extract_paciente_documento_nota(raw: Any) -> Optional[str]: - """Número de documento tras ``Paciente:`` y antes del guión del nombre.""" - t = _normalize_nota_text(raw) - if not t: - return None - m = _PACIENTE_DOC_NOTA_RE.search(t) - return m.group(1) if m else None - - -def extract_autorizacion_nota(raw: Any) -> Optional[str]: - """Número tras ``Autorización No.`` (opcional por fila).""" - t = _normalize_nota_text(raw) - if not t: - return None - m = _AUTORIZACION_NOTA_RE.search(t) - return m.group(1) if m else None - - -def _alloc_nota_extract_column_names( - col_name: str, existing: set[str] -) -> tuple[str, str]: - """Nombres únicos para documento paciente y autorización.""" - base = re.sub(r"[^\w]+", "_", str(col_name).strip(), flags=re.ASCII) or "col" - for i in range(0, 200): - suf = f"_{i}" if i else "" - dcol, acol = f"{base}{suf}_nota_paciente_doc", f"{base}{suf}_nota_autorizacion" - if dcol not in existing and acol not in existing: - return dcol, acol - raise ValueError("No se pudieron asignar nombres de columnas únicos.") - - -def _apply_nota_extract_columns(result: pd.DataFrame, col: str, log: list[str]) -> None: - """Añade columnas con documento (Paciente:) y número de autorización si existe.""" - dcol, acol = _alloc_nota_extract_column_names(str(col), set(result.columns)) - docs: list[Any] = [] - auths: list[Any] = [] - for v in result[col].tolist(): - dd = extract_paciente_documento_nota(v) - aa = extract_autorizacion_nota(v) - docs.append(dd if dd is not None else pd.NA) - auths.append(aa if aa is not None else pd.NA) - result[dcol] = docs - result[acol] = auths - log.append( - f"[OK] '{str(col)}' -> columnas '{dcol}' y '{acol}' <- Extraer datos de notas" - ) - - -# Registro de reglas -CLEANING_RULES: list[dict[str, Any]] = [ - { - "id": "strip_special_spaces", - "name": "Eliminar caracteres especiales y espacios", - "description": ( - "Deja solo caracteres alfanuméricos (A-Z, a-z, 0-9). No usar antes de " - "«Normalizar fecha» en columnas con fecha y hora: pega cifras y puede impedir " - "interpretar el valor (aplique primero la normalización de fecha o use la regla " - "que conserva separadores)." - ), - "fn": _rule_strip_special_spaces, - }, - { - "id": "strip_special_keep_date_seps", - "name": "Quitar símbolos; conserva /, -, : y espacios (fechas o texto con fechas)", - "description": ( - "Quita comas, $, comillas, etc. y deja letras, números, /, -, : (hora) y espacios. " - "Adecuado p. ej. en columna 'Fecha Servicio'." - ), - "fn": _rule_strip_special_keep_date_seps, - }, - { - "id": "strip_spaces", - "name": "Eliminar únicamente espacios", - "description": "Elimina todos los caracteres de espacio en blanco.", - "fn": _rule_strip_spaces, - }, - { - "id": "to_integer", - "name": "Convertir a Entero", - "description": "Convierte a enteros con detección de miles/decimal (`,`/`.`) y moneda; redondea decimales.", - "fn": _rule_to_integer, - }, - { - "id": "date_to_ddmmyyyy", - "name": "Normalizar fecha → DD/MM/AAAA", - "description": ( - "Normaliza fechas numéricas, con separadores, con ruido o con meses en " - "texto (ej. 26-abril-2026) al formato DD/MM/AAAA." - ), - "fn": _rule_date_to_ddmmyyyy, - }, - { - "id": "date_numeric_to_ddmmyyyy", - "name": "Conversión de formato numérico (Fechas) → DD/MM/AAAA", - "description": "Detecta el formato numérico predominante (YYYYMMDD, DDMMYYYY, etc.) y normaliza a DD/MM/AAAA.", - "fn": _rule_date_numeric_to_ddmmyyyy, - }, - { - "id": "date_alpha_to_ddmmyyyy", - "name": "Conversión de formato alfanumérico (Fechas) → DD/MM/AAAA", - "description": "Detecta el formato con separadores predominante (DD/MM/YYYY, MM/DD/YYYY, YYYY/MM/DD, etc.) y normaliza a DD/MM/AAAA.", - "fn": _rule_date_alpha_to_ddmmyyyy, - }, - { - "id": "date_messy_text_to_ddmmyyyy", - "name": "Texto a fecha (ruido en fechas) → DD/MM/AAAA", - "description": "Quita ruido entre cifras (*, +, _, /, etc.), detecta el orden en la columna (DDMMYYYY, YYYYMMDD, DDMMYY) y unifica a DD/MM/AAAA.", - "fn": _rule_date_messy_text_to_ddmmyyyy, - }, - { - "id": "round_decimals_custom", - "name": "Aproximación Decimales (>0,5 sube, ≤0,5 baja)", - "description": "Usa el mismo análisis de miles, decimales y moneda que «Entero». Tras leer el número, si la parte fraccionaria de |valor| es mayor a 0,5 se aproxima al entero hacia lejos de cero; en caso contrario, hacia cero. El 0,5 exacto se baja.", - "fn": _rule_round_decimals_custom, - }, - { - "id": "doc_id_split_tipo_numero", - "name": "Dividir tipo y número (documento)", - "description": ( - "Añade dos columnas (nombres únicos p. ej. TipoID_doc_tipo, TipoID_doc_numeros): " - "prefijo con letras (NIT, CC, CE, …) y cifras sin separadores. No modifica la columna de origen." - ), - "fn": _rule_passthrough_series, - }, - { - "id": "doc_type_homologate", - "name": "Homologar tipo de documento", - "description": ( - "Convierte tipos de documento escritos como nombre largo, sigla o texto con " - "símbolos a su abreviatura estándar (CC, TI, CE, RC, etc.)." - ), - "fn": _rule_doc_type_homologate, - }, - { - "id": "cop_mod_split", - "name": "Separa COP/MOD (copago / moderadora)", - "description": ( - "Según el año en la columna Fecha Servicio que elija en la tarjeta, clasifica " - "el importe en cuota moderadora (valores por año en Configuración) o copago. " - "Se ejecuta al final del proceso de limpieza para usar fechas ya normalizadas." - ), - "fn": _rule_passthrough_series, - }, - { - "id": "nota_paciente_autorizacion", - "name": "Extraer datos de notas (documento y autorización)", - "description": ( - "Desde texto tipo nota hospitalaria, extrae el número tras «Paciente:» (antes del " - "guión del nombre) y, si existe, el de «Autorización No.». Añade dos columnas; " - "no modifica el texto original." - ), - "fn": _rule_passthrough_series, - }, -] - -_RULE_BY_ID: dict[str, Callable[[pd.Series], pd.Series]] = { - r["id"]: r["fn"] for r in CLEANING_RULES -} - -_RULE_NAME_BY_ID: dict[str, str] = {r["id"]: r["name"] for r in CLEANING_RULES} - - -def get_rule(rule_id: str) -> Optional[dict[str, Any]]: - """Devuelve el registro de una regla por id.""" - for r in CLEANING_RULES: - if r["id"] == rule_id: - return r - return None - - -# --------------------------------------------------------------------------- -# APLICACIÓN DE REGLAS -# --------------------------------------------------------------------------- - - -def apply_rules( - df: pd.DataFrame, - rules_per_column: dict[str, list[str]], - *, - cop_mod_fecha_column: Optional[dict[str, str]] = None, - cop_mod_config: Optional[dict[str, Any]] = None, -) -> tuple[pd.DataFrame, list[str]]: - """Aplica las reglas seleccionadas a cada columna. - - `rules_per_column` es un dict de `columna -> lista de IDs de reglas en el - orden a aplicar`. La regla `cop_mod_split` se difiere a una segunda pasada - tras el resto, para que la columna de fecha pueda normalizarse antes. - - `cop_mod_fecha_column`: mapa columna de importe -> nombre de columna Fecha Servicio. - `cop_mod_config`: opcional; puede incluir ``moderadora_by_year`` (dict año → lista). - """ - result = df.copy() - log: list[str] = [] - - moderadora_years = merge_moderadora_config(cop_mod_config) - - def _run_rules_for_column(col: str, rule_ids: list[str]) -> None: - if not rule_ids: - return - if col not in result.columns: - log.append(f"[WARN] Columna no encontrada: {col}") - return - split_has_homologation = ( - "doc_id_split_tipo_numero" in rule_ids and "doc_type_homologate" in rule_ids - ) - for rule_id in rule_ids: - if rule_id == "doc_id_split_tipo_numero": - try: - _apply_doc_id_split_columns( - result, col, log, homologate_type=split_has_homologation - ) - except Exception as exc: # noqa: BLE001 - logger.exception("Error aplicando regla %s a %s", rule_id, col) - log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}") - continue - if rule_id == "nota_paciente_autorizacion": - try: - _apply_nota_extract_columns(result, col, log) - except Exception as exc: # noqa: BLE001 - logger.exception("Error aplicando regla %s a %s", rule_id, col) - log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}") - continue - if rule_id == "doc_type_homologate" and split_has_homologation: - continue - fn = _RULE_BY_ID.get(rule_id) - if fn is None: - log.append(f"[WARN] Regla desconocida '{rule_id}' en columna '{col}'") - continue - try: - result[col] = fn(result[col]) - log.append(f"[OK] '{col}' <- {_RULE_NAME_BY_ID.get(rule_id, rule_id)}") - except Exception as exc: # noqa: BLE001 - logger.exception("Error aplicando regla %s a %s", rule_id, col) - log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}") - - # Fase 1: todas las reglas excepto cop_mod_split - for col, rule_ids in rules_per_column.items(): - filtered = [r for r in rule_ids if r != "cop_mod_split"] - _run_rules_for_column(col, filtered) - - # Fase 2: Separa COP/MOD sobre el DataFrame ya transformado - fecha_map = cop_mod_fecha_column or {} - for col, rule_ids in rules_per_column.items(): - if "cop_mod_split" not in rule_ids: - continue - fecha_col = fecha_map.get(col) - if not fecha_col: - log.append( - f"[WARN] COP/MOD en '{col}': indique la columna «Fecha Servicio» en la tarjeta; se omite." - ) - continue - try: - _apply_cop_mod_split( - result, - col, - fecha_col, - log, - moderadora_by_year=moderadora_years, - ) - except Exception as exc: # noqa: BLE001 - logger.exception("Error aplicando COP/MOD a %s", col) - log.append(f"[ERROR] '{col}' regla 'cop_mod_split': {exc}") - - return result, log - - -# --------------------------------------------------------------------------- -# CARGA ROBUSTA DE CSV -# --------------------------------------------------------------------------- - -_CANDIDATE_DELIMITERS = ["|", ";", "\t", ",", ":"] - - -def detect_delimiter(filepath: str) -> str: - """Detecta el delimitador del CSV de forma robusta. - - Estrategia: - 1. Intentar `csv.Sniffer` con un sample grande. - 2. Si falla o el resultado parece incorrecto, contar ocurrencias por línea - de cada delimitador candidato y elegir el que produce el mayor número - de columnas estable entre líneas. - """ - encodings = ("utf-8", "utf-8-sig", "latin-1", "cp1252") - sample = "" - for enc in encodings: - try: - with open(filepath, "r", encoding=enc, errors="replace") as f: - sample = f.read(65536) - break - except Exception: - continue - - if not sample: - return "," - - try: - dialect = _csv.Sniffer().sniff(sample, delimiters="".join(_CANDIDATE_DELIMITERS)) - sniffed = dialect.delimiter - except _csv.Error: - sniffed = None - - lines = [ln for ln in sample.splitlines() if ln.strip()] - if not lines: - return sniffed or "," - - # Para cada candidato, calcular el número promedio de columnas y la - # consistencia (cuántas líneas tienen el mismo conteo dominante). - scores: dict[str, tuple[int, int]] = {} - for delim in _CANDIDATE_DELIMITERS: - counts = [ln.count(delim) for ln in lines[:50]] - if max(counts) == 0: - continue - # Modo (valor más frecuente) - common = Counter(counts).most_common(1)[0] - mode_count, mode_freq = common - if mode_count == 0: - continue - scores[delim] = (mode_count, mode_freq) - - if scores: - # Elegir el delimitador con mayor número de columnas (mode_count) y, - # como desempate, mayor consistencia (mode_freq). - best = max(scores.items(), key=lambda kv: (kv[1][0], kv[1][1])) - best_delim = best[0] - # Si el sniffer dio algo razonable y coincide, prevalece; si difiere y - # nuestro candidato es claramente mejor, usamos el nuestro. - if sniffed and sniffed in scores: - sniffed_score = scores[sniffed] - if sniffed_score[0] >= best[1][0] - 1: - return sniffed - return best_delim - - return sniffed or "," - - -def load_csv(filepath: str) -> tuple[pd.DataFrame, str]: - """Carga un CSV con detección de delimitador y tolerancia a filas malas. - - Devuelve `(df, delimiter)`. Lanza excepción si todos los intentos fallan. - """ - delimiter = detect_delimiter(filepath) - - encodings_to_try = ["utf-8", "utf-8-sig", "latin-1", "cp1252"] - last_error: Optional[Exception] = None - - for enc in encodings_to_try: - try: - df = pd.read_csv( - filepath, - delimiter=delimiter, - dtype=str, - encoding=enc, - engine="python", - on_bad_lines="skip", - ) - return df, delimiter - except Exception as exc: # noqa: BLE001 - last_error = exc - continue - - # Último intento con quoting permisivo y sin separador detectado - try: - df = pd.read_csv( - filepath, - sep=None, - dtype=str, - engine="python", - on_bad_lines="skip", - ) - return df, "(auto)" - except Exception as exc: # noqa: BLE001 - last_error = exc - - raise RuntimeError(f"No se pudo leer el CSV: {last_error}") diff --git a/services/cleaning/domain/csv_cleaning/__init__.py b/services/cleaning/domain/csv_cleaning/__init__.py new file mode 100644 index 0000000..52edbe3 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/__init__.py @@ -0,0 +1,64 @@ +"""Limpieza de CSV con análisis inteligente local y reglas configurables. + +API pública (estable): +- ``analyze_dataframe`` — resumen por columna +- ``CLEANING_RULES`` / ``register_rule`` / ``get_rule`` — catálogo de reglas +- ``apply_rules`` — aplica reglas por columna +- ``load_csv`` / ``load_csv_bytes`` / ``detect_delimiter`` — I/O + +Para agregar una regla nueva, ver ``rules/registry.py`` y ``rules/builtins.py``. +""" +from __future__ import annotations + +from .analyze import analyze_dataframe, sample_dataframe_rows +from .apply import apply_rules +from .cop_mod import ( + DEFAULT_MODERADORA_BY_YEAR, + merge_moderadora_config, + normalize_moderadora_by_year_dict, +) +from .documents import homologate_doc_type, split_doc_tipo_y_numero_texto +from .io import detect_delimiter, load_csv, load_csv_bytes +from .models import ( + TYPE_BOOLEAN, + TYPE_CATEGORICAL, + TYPE_DATE, + TYPE_DECIMAL, + TYPE_EMPTY, + TYPE_INTEGER, + TYPE_MIXED, + TYPE_TEXT, + ColumnSummary, +) +from .notas import extract_autorizacion_nota, extract_paciente_documento_nota +from .rules import CLEANING_RULES, get_rule, register_rule +from .type_inference import applicable_rule_ids_for_type + +__all__ = [ + "CLEANING_RULES", + "ColumnSummary", + "DEFAULT_MODERADORA_BY_YEAR", + "TYPE_BOOLEAN", + "TYPE_CATEGORICAL", + "TYPE_DATE", + "TYPE_DECIMAL", + "TYPE_EMPTY", + "TYPE_INTEGER", + "TYPE_MIXED", + "TYPE_TEXT", + "analyze_dataframe", + "applicable_rule_ids_for_type", + "apply_rules", + "detect_delimiter", + "extract_autorizacion_nota", + "extract_paciente_documento_nota", + "get_rule", + "homologate_doc_type", + "load_csv", + "load_csv_bytes", + "merge_moderadora_config", + "normalize_moderadora_by_year_dict", + "register_rule", + "sample_dataframe_rows", + "split_doc_tipo_y_numero_texto", +] diff --git a/services/cleaning/domain/csv_cleaning/analyze.py b/services/cleaning/domain/csv_cleaning/analyze.py new file mode 100644 index 0000000..ee051f4 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/analyze.py @@ -0,0 +1,94 @@ +"""Análisis de DataFrames: resumen por columna y muestras.""" +from __future__ import annotations + +import json +import random +from typing import Any + +import pandas as pd + +from .models import ( + TYPE_CATEGORICAL, + TYPE_DATE, + TYPE_DECIMAL, + TYPE_INTEGER, + TYPE_TEXT, + ColumnSummary, +) +from .numbers import _to_numeric_lenient +from .type_inference import _applicable_rules_for_type, _infer_column_type + + +def analyze_dataframe(df: pd.DataFrame) -> list[ColumnSummary]: + """Genera un resumen por columna del DataFrame.""" + summaries: list[ColumnSummary] = [] + total_rows = len(df) + rng = random.Random(42) + + for col in df.columns: + series = df[col] + null_count = int(series.isna().sum() + (series.astype(str).str.strip() == "").sum()) + # Evitar doble conteo: considerar vacío solo si no-NaN + null_count = int((series.isna() | (series.astype(str).str.strip() == "")).sum()) + null_pct = (null_count / total_rows * 100) if total_rows else 0.0 + + non_null = series.dropna().astype(str) + non_null = non_null[non_null.str.strip() != ""] + + example = "" + samples: list[str] = [] + if not non_null.empty: + values = non_null.tolist() + k = min(5, len(values)) + picked = rng.sample(values, k) if len(values) >= k else list(values) + for raw in picked: + text = str(raw) + if len(text) > 80: + text = text[:77] + "..." + samples.append(text) + example = samples[0] if samples else "" + + unique_count = int(non_null.nunique()) + unique_pct = (unique_count / total_rows * 100) if total_rows else 0.0 + + inferred_type, date_fmt = _infer_column_type(series, str(col)) + + summary = ColumnSummary( + name=str(col), + inferred_type=inferred_type, + example=example, + samples=samples, + null_count=null_count, + null_pct=null_pct, + unique_count=unique_count, + unique_pct=unique_pct, + detected_date_format=date_fmt, + ) + + if inferred_type in (TYPE_INTEGER, TYPE_DECIMAL): + numeric = _to_numeric_lenient(non_null) + if numeric.notna().any(): + summary.min = float(numeric.min()) + summary.max = float(numeric.max()) + summary.mean = float(numeric.mean()) + elif inferred_type in (TYPE_TEXT, TYPE_CATEGORICAL): + if not non_null.empty: + summary.avg_length = float(non_null.str.len().mean()) + counts = non_null.value_counts().head(3) + summary.top_values = [(str(v), int(c)) for v, c in counts.items()] + + summary.applicable_rules = _applicable_rules_for_type(inferred_type) + summaries.append(summary) + + return summaries + + +def sample_dataframe_rows(df: pd.DataFrame, n: int = 8) -> list[dict[str, Any]]: + """Devuelve hasta `n` filas aleatorias como dicts JSON-serializables.""" + if df.empty or n <= 0: + return [] + take = min(n, len(df)) + rng = random.Random(42) + indices = rng.sample(list(range(len(df))), take) + subset = df.iloc[indices] + return json.loads(subset.to_json(orient="records", date_format="iso")) diff --git a/services/cleaning/domain/csv_cleaning/apply.py b/services/cleaning/domain/csv_cleaning/apply.py new file mode 100644 index 0000000..72d96dd --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/apply.py @@ -0,0 +1,105 @@ +"""Aplicación de reglas seleccionadas sobre un DataFrame.""" +from __future__ import annotations + +import logging +from typing import Any, Optional + +import pandas as pd + +from .cop_mod import _apply_cop_mod_split, merge_moderadora_config +from .documents import _apply_doc_id_split_columns +from .notas import _apply_nota_extract_columns +from .rules import get_rule_fn, get_rule_name + +logger = logging.getLogger(__name__) + + +def apply_rules( + df: pd.DataFrame, + rules_per_column: dict[str, list[str]], + *, + cop_mod_fecha_column: Optional[dict[str, str]] = None, + cop_mod_config: Optional[dict[str, Any]] = None, +) -> tuple[pd.DataFrame, list[str]]: + """Aplica las reglas seleccionadas a cada columna. + + `rules_per_column` es un dict de `columna -> lista de IDs de reglas en el + orden a aplicar`. La regla `cop_mod_split` se difiere a una segunda pasada + tras el resto, para que la columna de fecha pueda normalizarse antes. + + `cop_mod_fecha_column`: mapa columna de importe -> nombre de columna Fecha Servicio. + `cop_mod_config`: opcional; puede incluir ``moderadora_by_year`` (dict año → lista). + """ + result = df.copy() + log: list[str] = [] + + moderadora_years = merge_moderadora_config(cop_mod_config) + + def _run_rules_for_column(col: str, rule_ids: list[str]) -> None: + if not rule_ids: + return + if col not in result.columns: + log.append(f"[WARN] Columna no encontrada: {col}") + return + split_has_homologation = ( + "doc_id_split_tipo_numero" in rule_ids and "doc_type_homologate" in rule_ids + ) + for rule_id in rule_ids: + if rule_id == "doc_id_split_tipo_numero": + try: + _apply_doc_id_split_columns( + result, col, log, homologate_type=split_has_homologation + ) + except Exception as exc: # noqa: BLE001 + logger.exception("Error aplicando regla %s a %s", rule_id, col) + log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}") + continue + if rule_id == "nota_paciente_autorizacion": + try: + _apply_nota_extract_columns(result, col, log) + except Exception as exc: # noqa: BLE001 + logger.exception("Error aplicando regla %s a %s", rule_id, col) + log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}") + continue + if rule_id == "doc_type_homologate" and split_has_homologation: + continue + fn = get_rule_fn(rule_id) + if fn is None: + log.append(f"[WARN] Regla desconocida '{rule_id}' en columna '{col}'") + continue + try: + result[col] = fn(result[col]) + log.append(f"[OK] '{col}' <- {get_rule_name(rule_id)}") + except Exception as exc: # noqa: BLE001 + logger.exception("Error aplicando regla %s a %s", rule_id, col) + log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}") + + # Fase 1: todas las reglas excepto cop_mod_split + for col, rule_ids in rules_per_column.items(): + filtered = [r for r in rule_ids if r != "cop_mod_split"] + _run_rules_for_column(col, filtered) + + # Fase 2: Separa COP/MOD sobre el DataFrame ya transformado + fecha_map = cop_mod_fecha_column or {} + for col, rule_ids in rules_per_column.items(): + if "cop_mod_split" not in rule_ids: + continue + fecha_col = fecha_map.get(col) + if not fecha_col: + log.append( + f"[WARN] COP/MOD en '{col}': indique la columna «Fecha Servicio» en la tarjeta; se omite." + ) + continue + try: + _apply_cop_mod_split( + result, + col, + fecha_col, + log, + moderadora_by_year=moderadora_years, + ) + except Exception as exc: # noqa: BLE001 + logger.exception("Error aplicando COP/MOD a %s", col) + log.append(f"[ERROR] '{col}' regla 'cop_mod_split': {exc}") + + return result, log diff --git a/services/cleaning/domain/csv_cleaning/column_hints.py b/services/cleaning/domain/csv_cleaning/column_hints.py new file mode 100644 index 0000000..342b95b --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/column_hints.py @@ -0,0 +1,210 @@ +"""Heurísticas por nombre de columna y presentación del contenido.""" +from __future__ import annotations + +import re +from typing import Optional + +import pandas as pd + +from .nullish import _is_nullish_scalar + + +def _column_name_suggests_date_like(name: str) -> bool: + """Columnas cuyo nombre pide tratar el contenido como fecha a priori.""" + n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() + if not n: + return False + if re.search( + r"\b(fecha|date|año|mes|dia|día|f[_-]?(alta|baja|proc|corte|pago|ini|fin|" + r"desde|hasta|vigenc|vence|venc|caduc|f\s?ec)\b|periodo|anio|ano|expir)", + n, + ) or n.startswith("fec_") or n.startswith("fecha"): + return True + return False + + +def _column_name_suggests_monetary_value(name: str) -> bool: + """importe, valor, neto, IVA, etc. — no interpretar cifras como fechas. + + Encabezados de tipo `fecha_…` no se tratan como moneda salvo mezcla con monto/valor. + """ + n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() + if not n: + return False + if _column_name_suggests_date_like(name) and not re.search( + r"\b(monto|valor|importe|pago|total|bruto|neto|import|unit|precio|copago)\b", n + ): + return False + for key in ( + "monto", "importe", "import", "bruto", "neto", "subtotal", "sub total", + "total", "unitario", "unidad", "valor", "precio", "pago", "copago", "sald", "dcto", + "franq", "cuota", "iva", "tarif", "costo", "carg", "reten", "retencion", + ): + if key in n: + if key == "iva" and re.search(r"(priva|giva|diva|viva|cliva)\b", n): + continue + if key == "carg" and re.search(r"descarg|recarg|abord", n): + continue + return True + return False + + +def _column_name_suggests_code_id(name: str) -> bool: + """Código, ref., radicado, tipoynúm de documento — no forzar 6-8 cifras como fechas (DDMMYY, etc.).""" + n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() + if not n or _column_name_suggests_date_like(name): + return False + for key in ( + "codigo", "código", "codig", "códig", "cód_", + "sku", "ean", "upc", "isbn", "upn", + "ref", "referen", "radic", "nro_fac", "nro fac", "nit", "cédul", "cedul", + "autoriz", "correlat", "secuenc", "línea de", "linea de", "cupo", + "tipy", "tipi", "tipynum", "typnum", "type_n", "n_doc", "n_docu", + "núm doc", "n_docu", "doc_id", "docum", "id_doc", + ): + if key in n: + return True + if re.search( + r"tipo[_\s\\-]*(&|/|u|e|y)[_\s\\-]*num|tipo[_\s\\-]+doc|tipo_y_n", + n, + re.IGNORECASE, + ) or n.startswith("tip_"): + return True + if n.startswith("cod_") or n.startswith("cód_") or n.startswith("ref_"): + return True + return bool(re.search(r"\b(nro[._]fac|#?\s*autor|#?\s*ref)\b", n, re.IGNORECASE)) + + +def _column_name_suggests_quantity_or_number_not_bool(name: str) -> bool: + """Nombres típicos de cantidad, importe, fechas: no inferir bool por 0/1.""" + n = re.sub(r"[_\s.]+", " ", (name or "")).lower().strip() + if not n: + return False + if _column_name_suggests_monetary_value(name) or _column_name_suggests_code_id(name): + return True + for key in ( + "fecha", + "monto", + "importe", + "bruto", + "neto", + "subtotal", + "unitario", + "unidad", + "valor", + "precio", + "pago", + "copago", + "cantid", + "quantity", + "qty", + "nro", + "núm", + "n°", + ): + if key in n: + return True + return False + + +def _raw_strings_suggest_monetary_presentation(non_null: pd.Series) -> bool: + """$ €, miles 1,234,12 o .00: presentación monetaria aunque el float sea "entero".""" + if non_null is None or len(non_null) == 0: + return False + n_hit = 0 + n_seen = 0 + for raw in non_null.head(500): + if _is_nullish_scalar(raw): + continue + t = str(raw) + s = t.strip() + n_seen += 1 + if re.search(r"[$\u00a3\u20ac\uffe5\uffe6\ufe69\uff04]", t): + n_hit += 1 + continue + if re.search(r"\b\d{1,3}(?:[.,\u00a0]\d{3})+\b", s) or re.search( + r"(?= 0.10 + + +def _cell_looks_cifra_con_moneda_iso_al_final(s: str) -> bool: + """Solo cifra/símbolo (no prefijo de documento) y código de moneda al final. + + Excluye filas al estilo `CC12…` (típico alfanumérico). + """ + t = str(s).strip() + u = t.upper() + if not re.search( + r"(?i)(USD|COP|EUR|CLP|ARS|GBP|MXN|PEN|DOP|UYU|VES|CRC|PYG|BRL|BOB|GTQ)\b\s*\Z", u + ): + return False + if re.match( + r"^(?i)(CC|CE|NIT|NUIP|TI|PA|MS|RC|DNI|NUIP|CN|SC|AS|PE|PT)\D*[\d0]", u + ): + return False + return re.match( + r"^[\$€0-9\s,.\-+%/]", t, + ) is not None + + +def _raw_strings_suggest_alnum_mixed_presentation(non_null: pd.Series) -> bool: + """Letras y dígitos juntos (p. ej. `CC32…`, `A1-2B`); no es puro 'entero/decimal' lógicamente. + + Excluido: columnas con apariencia monetaria, y celdas solo cifra + ISO al final (100 USD). + """ + if _raw_strings_suggest_monetary_presentation(non_null): + return False + n_m = 0 + n_s = 0 + for raw in non_null.head(500): + if _is_nullish_scalar(raw): + continue + t = str(raw).strip() + n_s += 1 + if not re.search(r"\d", t) or not re.search( + r"[A-Za-zÑñ\u00c0-\u024f]", t, re.IGNORECASE + ): + continue + if _cell_looks_cifra_con_moneda_iso_al_final(t): + continue + n_m += 1 + if n_s == 0: + return False + return n_m / n_s >= 0.20 + + +def _cell_looks_like_documento_id_tipo_nit(s: str) -> bool: + """Cadenas estilo `CC32324029`, `CE 1.234.567` — no son fechas (evita 8 cifras como DDMM). + + Al menos una letra al inicio y 5-16 dígitos en la celda (no `Jan`+`2020` = 4 dígitos). + """ + t = str(s).strip() + if not t or len(t) < 3: + return False + if not re.match(r"^[A-Za-z]{1,3}", t): + return False + dig = re.sub(r"[^\d]", "", t) + if len(dig) < 5 or len(dig) > 16: + return False + return True + + +def _content_looks_like_doc_id_letter_prefix_column(non_null: pd.Series) -> bool: + """Muestras con prefijo documental (CC, NIT, …) y ≥5 dígitos → no columna de fecha.""" + n_ok = 0 + n_seen = 0 + for raw in non_null.head(500): + if _is_nullish_scalar(raw): + continue + s = str(raw).strip() + n_seen += 1 + if _cell_looks_like_documento_id_tipo_nit(s): + n_ok += 1 + if n_seen == 0: + return False + return n_ok / n_seen >= 0.20 + diff --git a/services/cleaning/domain/csv_cleaning/cop_mod.py b/services/cleaning/domain/csv_cleaning/cop_mod.py new file mode 100644 index 0000000..0ffae04 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/cop_mod.py @@ -0,0 +1,162 @@ +"""Separación de copago / cuota moderadora por año.""" +from __future__ import annotations + +import re +from typing import Any, Literal, Optional + +import numpy as np +import pandas as pd + +from .dates import ( + _detect_compact_date_formats, + _parse_any_date_to_tuple, + detect_date_format, +) +from .numbers import _infer_decimal_hint_from_column, _parse_lenient_one + + +DEFAULT_MODERADORA_BY_YEAR: dict[int, set[int]] = { + 2023: {4100, 16400, 43000}, + 2024: {4500, 18200, 47700}, + 2025: {4700, 19200, 50300}, + 2026: {5000, 20100, 52800}, +} + + +def normalize_moderadora_by_year_dict(raw: Any) -> dict[int, set[int]]: + """Normaliza un dict JSON año → lista de enteros (cuotas moderadora).""" + if not isinstance(raw, dict): + return {} + out: dict[int, set[int]] = {} + for k, v in raw.items(): + try: + yi = int(k) + except (TypeError, ValueError): + continue + if not isinstance(v, (list, tuple)): + continue + nums: set[int] = set() + for x in v: + try: + nums.add(int(round(float(x)))) + except (TypeError, ValueError): + continue + if nums: + out[yi] = nums + return out + + +def merge_moderadora_config( + cop_mod_config: Optional[dict[str, Any]], +) -> dict[int, set[int]]: + """Fusiona defaults con `moderadora_by_year` opcional del usuario.""" + merged = {y: set(vals) for y, vals in DEFAULT_MODERADORA_BY_YEAR.items()} + if cop_mod_config and isinstance(cop_mod_config.get("moderadora_by_year"), dict): + custom = normalize_moderadora_by_year_dict( + cop_mod_config["moderadora_by_year"] + ) + for y, vals in custom.items(): + merged[y] = set(vals) + return merged + + +def _alloc_cop_mod_column_names( + col_name: str, existing: set[str] +) -> tuple[str, str]: + """Nombres únicos para copago y moderadora.""" + base = re.sub(r"[^\w]+", "_", str(col_name).strip(), flags=re.ASCII) or "col" + for i in range(0, 200): + suf = f"_{i}" if i else "" + ccol, mcol = f"{base}{suf}_copago", f"{base}{suf}_moderadora" + if ccol not in existing and mcol not in existing: + return ccol, mcol + raise ValueError("No se pudieron asignar nombres de columnas únicos.") + + +def _amount_cell_to_optional_int( + val: Any, + decimal_hint: Optional[Literal[",", "."]], +) -> Optional[int]: + try: + f = _parse_lenient_one(val, decimal_hint) + except Exception: + return None + if f is None or (isinstance(f, float) and not np.isfinite(f)) or ( + isinstance(f, (float, np.floating)) and np.isnan(f) + ): + return None + try: + return int(round(float(f))) + except (TypeError, ValueError, OverflowError): + return None + + +def _apply_cop_mod_split( + result: pd.DataFrame, + amount_col: str, + fecha_column: str, + log: list[str], + *, + moderadora_by_year: dict[int, set[int]], +) -> None: + """Añade columnas copago y moderadora según cuotas por año de la fecha.""" + if fecha_column not in result.columns: + log.append( + f"[WARN] COP/MOD: columna de fecha '{fecha_column}' no existe; se omite." + ) + return + if amount_col not in result.columns: + log.append(f"[WARN] COP/MOD: columna de importe '{amount_col}' no existe; se omite.") + return + + fec_ser = result[fecha_column] + amt_ser = result[amount_col] + stripped = fec_ser.astype(str).str.replace(r"[\s\/\-\.]", "", regex=True) + stripped = stripped.where(fec_ser.notna(), other=pd.NA) + sep_fmt = detect_date_format(fec_ser) or detect_date_format(stripped) + compact8_fmt, compact6_fmt = _detect_compact_date_formats(fec_ser) + amt_hint = _infer_decimal_hint_from_column(amt_ser.astype(str)) + + ccol, mcol = _alloc_cop_mod_column_names(str(amount_col), set(result.columns)) + cop_vals: list[Any] = [] + mod_vals: list[Any] = [] + + for i in range(len(result)): + amt_raw = amt_ser.iloc[i] + fec_raw = fec_ser.iloc[i] + amt_i = _amount_cell_to_optional_int(amt_raw, amt_hint) + if amt_i is None: + cop_vals.append(0) + mod_vals.append(0) + continue + + parsed_d = _parse_any_date_to_tuple( + fec_raw, + sep_fmt=sep_fmt, + compact8_fmt=compact8_fmt, + compact6_fmt=compact6_fmt, + ) + if parsed_d is None: + cop_vals.append(amt_i) + mod_vals.append(0) + continue + _d, _m, year = parsed_d + tiers = moderadora_by_year.get(year) + if tiers is None: + cop_vals.append(amt_i) + mod_vals.append(0) + continue + + if amt_i in tiers: + cop_vals.append(0) + mod_vals.append(amt_i) + else: + cop_vals.append(amt_i) + mod_vals.append(0) + + result[ccol] = cop_vals + result[mcol] = mod_vals + log.append( + f"[OK] '{amount_col}' -> '{ccol}', '{mcol}' <- Separa COP/MOD " + f"(fecha: '{fecha_column}')" + ) diff --git a/services/cleaning/domain/csv_cleaning/dates.py b/services/cleaning/domain/csv_cleaning/dates.py new file mode 100644 index 0000000..2c50d39 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/dates.py @@ -0,0 +1,459 @@ +"""Detección y normalización de fechas.""" +from __future__ import annotations + +import re +import unicodedata +from datetime import date +from typing import Any, Optional + +import pandas as pd + +from .nullish import _is_nullish_scalar + +_DATE_NUM_RE = re.compile(r"^\s*(\d{1,4})[\s\/\-\.](\d{1,2})[\s\/\-\.](\d{1,4})\s*$") +_DATE_COMPACT_RE = re.compile(r"^\s*(\d{6}|\d{8})\s*$") +# Hora opcional: HH[:MM[:SS]][ AM|PM] al final de la cadena +_TIME_TAIL_RE = re.compile( + r"[\sT]+(\d{1,2})(?::(\d{1,2})(?::(\d{1,2}))?)?\s*([APap]\.?\s*[Mm]\.?)?\s*$" +) + +_MONTH_NAME_TO_NUM = { + "enero": 1, + "ene": 1, + "febrero": 2, + "feb": 2, + "marzo": 3, + "mar": 3, + "abril": 4, + "abr": 4, + "mayo": 5, + "may": 5, + "junio": 6, + "jun": 6, + "julio": 7, + "jul": 7, + "agosto": 8, + "ago": 8, + "septiembre": 9, + "setiembre": 9, + "sep": 9, + "sept": 9, + "octubre": 10, + "oct": 10, + "noviembre": 11, + "nov": 11, + "diciembre": 12, + "dic": 12, +} + + +def _split_date_time(value: str) -> tuple[str, Optional[str]]: + """Separa la parte de fecha de la parte de hora si está presente. + + Devuelve `(date_part, time_part_normalized)`. `time_part_normalized` es + None si no hay hora. La hora se normaliza a `HH:MM[:SS][ AM|PM]`. + """ + if value is None: + return "", None + s = str(value).strip() + if not s: + return "", None + + m = _TIME_TAIL_RE.search(s) + if not m: + return s, None + + hh = int(m.group(1)) + mm = int(m.group(2)) if m.group(2) else 0 + ss = m.group(3) + ampm = m.group(4) + if not (0 <= hh <= 23 and 0 <= mm <= 59): + return s, None + if ss is not None: + try: + ssi = int(ss) + if not (0 <= ssi <= 59): + return s, None + except ValueError: + return s, None + # Validar contra AM/PM (con AM/PM, hora 1-12) + if ampm and not (1 <= hh <= 12): + return s, None + + # Quitar separadores habituales entre fecha y hora (p. ej. "12/27/24, 12:00 AM"). + date_part = s[: m.start()].rstrip(" \t,;T") + if not date_part: + return s, None + + parts: list[str] = [f"{hh:02d}:{mm:02d}"] + if ss is not None: + parts[0] += f":{int(ss):02d}" + if ampm: + norm_ampm = ampm.replace(".", "").replace(" ", "").upper() + if norm_ampm in ("AM", "PM"): + parts.append(norm_ampm) + time_norm = " ".join(parts) + return date_part, time_norm + + +def _extract_date_parts(value: str) -> Optional[tuple[int, int, int, str]]: + """Intenta extraer (a, b, c, layout) de una fecha en cualquier formato común. + + `layout` es uno de: 'sep' (con separadores), 'compact8' (YYYYMMDD o + DDMMYYYY), 'compact6' (DDMMYY o YYMMDD). No interpreta todavía qué + componente es día/mes/año; eso lo decide `detect_date_format` viendo la + columna completa. Acepta valores con hora al final (ignorada para los + componentes de fecha). + """ + if value is None: + return None + s = str(value).strip() + if not s or s.lower() in ("nan", "none", "nat"): + return None + + date_part, _ = _split_date_time(s) + if date_part: + s = date_part + + m = _DATE_NUM_RE.match(s) + if m: + try: + a, b, c = int(m.group(1)), int(m.group(2)), int(m.group(3)) + return (a, b, c, "sep") + except ValueError: + return None + + m2 = _DATE_COMPACT_RE.match(s) + if m2: + digits = m2.group(1) + if len(digits) == 8: + return (int(digits[0:2]), int(digits[2:4]), int(digits[4:8]), "compact8") + if len(digits) == 6: + return (int(digits[0:2]), int(digits[2:4]), int(digits[4:6]), "compact6") + + return None + + +def detect_date_format(series: pd.Series, sample_size: int = 500) -> Optional[str]: + """Analiza la serie y determina el formato de fecha dominante. + + Devuelve un string con el layout detectado (p. ej. "DD/MM/YYYY", + "MM/DD/YYYY", "YYYYMMDD", "DDMMYYYY") o None si no parece fechas. + """ + if series is None or len(series) == 0: + return None + + non_null = series.dropna() + if non_null.empty: + return None + + if len(non_null) > sample_size: + non_null = non_null.sample(sample_size, random_state=42) + + layouts = {"sep": 0, "compact8": 0, "compact6": 0} + sep_first_gt12 = 0 # primer componente > 12 → probable día + sep_second_gt12 = 0 # segundo componente > 12 → probable mes solo posible si MM/DD + sep_third_4digits = 0 # tercer componente de 4 dígitos → año al final + sep_first_4digits = 0 # primer componente de 4 dígitos → año al inicio + compact8_year_first = 0 # primeros 4 dígitos > 1900 → YYYYMMDD + compact8_year_last = 0 # últimos 4 dígitos > 1900 → DDMMYYYY + total_parsed = 0 + + for val in non_null: + parsed = _extract_date_parts(val) + if parsed is None: + continue + total_parsed += 1 + a, b, c, layout = parsed + layouts[layout] = layouts.get(layout, 0) + 1 + + if layout == "sep": + # año puede estar al inicio o al final según cantidad de dígitos + if _digit_count(a) == 4: + sep_first_4digits += 1 + if _digit_count(c) == 4: + sep_third_4digits += 1 + if a > 12: + sep_first_gt12 += 1 + if b > 12: + sep_second_gt12 += 1 + elif layout == "compact8": + # a son los primeros 2 dígitos, c son los últimos 4 + if c >= 1900: + compact8_year_last += 1 + # a concatenado con b son los primeros 4 dígitos + first4 = a * 100 + b + if first4 >= 1900: + compact8_year_first += 1 + + if total_parsed == 0: + return None + + # Requerimos que >=70% de los no nulos parseen como fecha para considerarla + # columna de fechas. + if total_parsed / len(non_null) < 0.7: + return None + + dominant = max(layouts, key=lambda k: layouts[k]) + + if dominant == "compact8": + if compact8_year_first > compact8_year_last: + return "YYYYMMDD" + return "DDMMYYYY" + if dominant == "compact6": + return "DDMMYY" + + # Caso con separadores + if sep_first_4digits > sep_third_4digits: + return "YYYY/MM/DD" + # año al final + if sep_first_gt12 > 0 and sep_second_gt12 == 0: + return "DD/MM/YYYY" + if sep_second_gt12 > 0 and sep_first_gt12 == 0: + return "MM/DD/YYYY" + # ambiguo → preferencia hispana + return "DD/MM/YYYY" + + +def _digit_count(n: int) -> int: + return len(str(abs(n))) + + +def _parse_date_with_format(value: str, fmt: str) -> Optional[tuple[int, int, int]]: + """Convierte un valor de fecha a (día, mes, año) según el layout detectado.""" + parsed = _extract_date_parts(value) + if parsed is None: + return None + a, b, c, layout = parsed + + if fmt == "DD/MM/YYYY": + d, m, y = a, b, c + elif fmt == "MM/DD/YYYY": + m, d, y = a, b, c + elif fmt == "YYYY/MM/DD": + y, m, d = a, b, c + elif fmt == "YYYYMMDD" and layout == "compact8": + # reconstruir: primer4 = a*100 + b, luego mes y día + y = a * 100 + b + m = c // 100 + d = c % 100 + elif fmt == "DDMMYYYY" and layout == "compact8": + d = a + m = b + y = c + elif fmt == "DDMMYY" and layout == "compact6": + d = a + m = b + y = 2000 + c if c < 50 else 1900 + c + else: + # intentar como sep genérico + d, m, y = a, b, c + + if not (1 <= m <= 12 and 1 <= d <= 31 and 1000 <= y <= 9999): + # año en 2 dígitos + if y < 100: + y = 2000 + y if y < 50 else 1900 + y + if not (1 <= m <= 12 and 1 <= d <= 31 and 1000 <= y <= 9999): + return None + return (d, m, y) + + +def _normalize_date_text(value: Any) -> str: + text = unicodedata.normalize("NFKD", str(value or "")) + text = "".join(c for c in text if not unicodedata.combining(c)) + return text.lower() + + +def _valid_date_tuple(d: int, m: int, y: int) -> Optional[tuple[int, int, int]]: + if y < 100: + y = 2000 + y if y < 50 else 1900 + y + if not (1000 <= y <= 9999): + return None + try: + date(y, m, d) + except ValueError: + return None + return (d, m, y) + + +def _split_glued_year_suffix_hhmm(digits: str) -> Optional[tuple[int, int, int]]: + """Detecta `YYYY` + `HHMM` en una sola tira de 8 cifras (p. ej. `20241344`). + + Tras quitar separadores puede quedar pegado al mes (p. ej. `15may20241344`). + Devuelve (año, hora, minuto) si la parte horaria es válida; si no, None. + """ + if len(digits) != 8 or not digits.isdigit(): + return None + y = int(digits[:4]) + hh = int(digits[4:6]) + mm = int(digits[6:8]) + if not (1000 <= y <= 9999 and 0 <= hh <= 23 and 0 <= mm <= 59): + return None + return (y, hh, mm) + + +def _parse_text_month_candidate( + first: tuple[int, str], + month: int, + second: tuple[int, str], + *, + layout: Literal["around", "after", "before"], +) -> Optional[tuple[int, int, int]]: + a, astr = first + b, bstr = second + if layout == "around": + if len(astr) == 4: + return _valid_date_tuple(b, month, a) + if len(bstr) == 4: + return _valid_date_tuple(a, month, b) + return _valid_date_tuple(a, month, b) + if layout == "after": + if len(astr) == 4: + return _valid_date_tuple(b, month, a) + return _valid_date_tuple(a, month, b) + if len(astr) == 4: + return _valid_date_tuple(b, month, a) + return _valid_date_tuple(a, month, b) + + +def _parse_date_with_text_month(raw: Any) -> Optional[tuple[int, int, int]]: + if _is_nullish_scalar(raw): + return None + date_part, _ = _split_date_time(str(raw)) + normalized = _normalize_date_text(date_part or raw) + # `\d{8}` antes que `\d{1,4}` para no partir año+hora pegados tras strip agresivo. + tokens = re.findall(r"[a-z]+|\d{8}|\d{1,4}", normalized) + if not tokens: + return None + + for i, token in enumerate(tokens): + month = _MONTH_NAME_TO_NUM.get(token) + if month is None: + continue + nums_before = [ + (int(t), t) for t in tokens[:i] if t.isdigit() + ] + nums_after = [ + (int(t), t) for t in tokens[i + 1 :] if t.isdigit() + ] + candidates: list[Optional[tuple[int, int, int]]] = [] + if nums_before and len(nums_after) == 1: + glued = _split_glued_year_suffix_hhmm(nums_after[0][1]) + if glued is not None: + y, _hh, _mm = glued + candidates.append( + _parse_text_month_candidate( + nums_before[-1], month, (y, str(y)), layout="around" + ) + ) + if nums_before and nums_after: + candidates.append( + _parse_text_month_candidate( + nums_before[-1], month, nums_after[0], layout="around" + ) + ) + if len(nums_after) >= 2: + candidates.append( + _parse_text_month_candidate( + nums_after[0], month, nums_after[1], layout="after" + ) + ) + if len(nums_before) >= 2: + candidates.append( + _parse_text_month_candidate( + nums_before[-2], month, nums_before[-1], layout="before" + ) + ) + for parsed in candidates: + if parsed is not None: + return parsed + return None + + + +def _detect_compact_date_formats( + series: pd.Series, +) -> tuple[Optional[str], Optional[str]]: + cser = series.map(_normalize_messy_date_to_compact) + c8s = pd.Series( + [x for x in cser.tolist() if isinstance(x, str) and len(x) == 8] + ) + c6s = pd.Series( + [x for x in cser.tolist() if isinstance(x, str) and len(x) == 6] + ) + fmt8 = detect_date_format(c8s) if not c8s.empty else None + fmt6 = detect_date_format(c6s) if not c6s.empty else None + return fmt8, fmt6 + + +def _parse_any_date_to_tuple( + raw: Any, + *, + sep_fmt: Optional[str], + compact8_fmt: Optional[str], + compact6_fmt: Optional[str], +) -> Optional[tuple[int, int, int]]: + parsed_text = _parse_date_with_text_month(raw) + if parsed_text is not None: + return parsed_text + + if _is_nullish_scalar(raw): + return None + + raw_text = str(raw) + date_part, _ = _split_date_time(raw_text) + target = date_part if date_part else raw_text + if sep_fmt: + parsed = _parse_date_with_format(target, sep_fmt) + if parsed is not None: + return parsed + for fallback_fmt in ("DD/MM/YYYY", "MM/DD/YYYY", "YYYY/MM/DD"): + if fallback_fmt == sep_fmt: + continue + parsed = _parse_date_with_format(target, fallback_fmt) + if parsed is not None: + return parsed + + compact = _normalize_messy_date_to_compact(raw) + if not isinstance(compact, str): + return None + if len(compact) == 8: + for fmt in (compact8_fmt, "DDMMYYYY", "YYYYMMDD"): + if not fmt: + continue + parsed = _parse_date_with_format(compact, fmt) + if parsed is not None: + return parsed + if len(compact) == 6: + parsed = _parse_date_with_format(compact, compact6_fmt or "DDMMYY") + if parsed is not None: + return parsed + return None + + +def _normalize_messy_date_to_compact(raw: Any) -> Optional[str]: + """Quita ruido (* + _ / espacios, etc.); deja 6 o 8 dígitos o None. + + Soporta separadores entremezclados; hora al final vía `_split_date_time`. + Con 9+ dígitos, heurística: tomar 8 iniciales (p. ej. 9.º = 0 colado) y + validar luego con `_parse_date_with_format`; con más de 8 sobrantes, + 8 iniciales. + """ + if _is_nullish_scalar(raw): + return None + s = str(raw).strip() + if s.lower() in ("nan", "none", "nat", ""): + return None + date_part, _ = _split_date_time(s) + body = date_part if date_part else s + d = re.sub(r"[^\d]", "", body) + if not d: + return None + n = len(d) + if n in (6, 8): + return d + if n < 6 or n == 7: + return None + # 9+ dígitos: 8 iniciales (p. ej. 9.º 0 sobrante, o ruido al final) + return d[:8] + diff --git a/services/cleaning/domain/csv_cleaning/documents.py b/services/cleaning/domain/csv_cleaning/documents.py new file mode 100644 index 0000000..a32e3ce --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/documents.py @@ -0,0 +1,175 @@ +"""Homologación y división de tipo/número de documento.""" +from __future__ import annotations + +import re +import unicodedata +from typing import Any, Optional + +import pandas as pd + +from .nullish import _is_nullish_scalar + + +_DOC_TYPE_ALIASES: dict[str, tuple[str, ...]] = { + "CC": ("CC", "Cedula", "CD", "C", "C.C", "C C", "CCC"), + "TI": ("TI", "Tarjeta", "Tarjeta Identidad", "T1", "T", "TL"), + "CN": ("CN", "Certificado", "Nacido Vivo", "Certificado Nacido Vivo"), + "CE": ("CE", "Cedula de Extranjeria", "Extranjera"), + "ME": ("ME", "Menor sin Identificacion"), + "PS": ("PS", "Pasaporte"), + "PT": ("PT", "Permiso por proteccion Temporal"), + "RC": ("RC", "Registro Civil", "Registro", "TC"), + "SC": ("SC", "Salvo Conducto", "Salvoconducto"), + "PE": ("PE", "Permiso especial de permanencia", "Permiso"), +} + + +def _ascii_upper(value: Any) -> str: + text = unicodedata.normalize("NFKD", str(value or "")) + text = "".join(c for c in text if not unicodedata.combining(c)) + return text.upper() + + +def _normalize_doc_type_key(value: Any) -> str: + """Clave comparable: sin acentos, espacios, puntos ni símbolos.""" + return re.sub(r"[^A-Z0-9]", "", _ascii_upper(value)) + + +def _clean_doc_type_text(value: Any) -> Optional[str]: + cleaned = re.sub(r"[^A-Z0-9]+", " ", _ascii_upper(value)).strip() + cleaned = re.sub(r"\s+", " ", cleaned) + return cleaned or None + + +_DOC_TYPE_ALIAS_BY_KEY: dict[str, str] = { + _normalize_doc_type_key(alias): canonical + for canonical, aliases in _DOC_TYPE_ALIASES.items() + for alias in aliases +} + +_DOC_TYPE_ALIAS_DISPLAY_BY_KEY: dict[str, str] = {} +for _canonical, _aliases in _DOC_TYPE_ALIASES.items(): + for _alias in _aliases: + _alias_key = _normalize_doc_type_key(_alias) + _alias_display = _clean_doc_type_text(_alias) + if _alias_key and _alias_display: + _DOC_TYPE_ALIAS_DISPLAY_BY_KEY.setdefault(_alias_key, _alias_display) + +_DOC_TYPE_ALIAS_KEYS_SORTED: list[str] = sorted( + _DOC_TYPE_ALIAS_BY_KEY, + key=lambda k: (len(k), k), + reverse=True, +) + + +def homologate_doc_type(value: Any) -> Optional[str]: + """Homologa un tipo documental a sigla; si no existe, conserva texto limpio.""" + if _is_nullish_scalar(value): + return None + key = _normalize_doc_type_key(value) + if not key or key.lower() in ("nan", "none", "nat"): + return None + return _DOC_TYPE_ALIAS_BY_KEY.get(key) or _clean_doc_type_text(value) + + +def _compact_doc_text(value: Any) -> str: + return _normalize_doc_type_key(value) + + +def _looks_like_short_alias_prefix(compact: str, alias_key: str) -> bool: + """Evita que alias de 1-2 chars capturen palabras como CODIGO o TOTAL.""" + if not compact.startswith(alias_key): + return False + rest = compact[len(alias_key):] + if not rest: + return True + return bool(rest[0].isdigit()) + + +def _match_known_doc_type_prefix(compact: str) -> Optional[tuple[str, str]]: + for alias_key in _DOC_TYPE_ALIAS_KEYS_SORTED: + if len(alias_key) <= 2: + if not _looks_like_short_alias_prefix(compact, alias_key): + continue + elif not compact.startswith(alias_key): + continue + return _DOC_TYPE_ALIAS_BY_KEY[alias_key], alias_key + return None + + +def split_doc_tipo_y_numero_texto( + raw: Any, + *, + homologate: bool = True, +) -> tuple[Optional[str], Optional[str]]: + """Separa tipo documental y número, tolerando nombres largos y símbolos. + + Si `homologate` es True, tipos conocidos se devuelven como sigla (CC, TI, + etc.). Los tipos desconocidos se conservan limpios en mayúsculas. + """ + if _is_nullish_scalar(raw) and not (isinstance(raw, str) and not str(raw).strip()): + return (None, None) + t = str(raw).strip() + if t.lower() in ("nan", "none", "nat", ""): + return (None, None) + if t and t[0].isdigit(): + return (None, None) + + compact = _compact_doc_text(t) + if not compact: + return (None, None) + + match = _match_known_doc_type_prefix(compact) + if match is not None: + canonical, alias_key = match + rest = compact[len(alias_key):] + nums = re.sub(r"[^\d]", "", rest) + if homologate: + return (canonical, nums if nums else None) + raw_type = _DOC_TYPE_ALIAS_DISPLAY_BY_KEY.get(alias_key) or canonical + return (raw_type, nums if nums else None) + + m = re.match( + r"^([^\d]+?)\s*([0-9].*)\s*$", + t, + ) + if m: + to_t = _clean_doc_type_text(m.group(1)) + nums = re.sub(r"[^\d]", "", m.group(2)) + return (homologate_doc_type(to_t) if homologate else to_t, nums if nums else None) + + if not re.search(r"\d", t): + to_t = homologate_doc_type(t) if homologate else _clean_doc_type_text(t) + return (to_t, None) + return (None, None) + + +def _alloc_doc_split_column_names( + col_name: str, existing: set[str] +) -> tuple[str, str]: + """Nombres únicos para las dos columnas derivadas.""" + base = re.sub(r"[^\w]+", "_", str(col_name).strip(), flags=re.ASCII) or "col" + for i in range(0, 200): + suf = f"_{i}" if i else "" + t, n = f"{base}{suf}_doc_tipo", f"{base}{suf}_doc_numeros" + if t not in existing and n not in existing: + return t, n + raise ValueError("No se pudieron asignar nombres de columnas únicos.") + + +def _apply_doc_id_split_columns( + result: pd.DataFrame, col: str, log: list[str], *, homologate_type: bool = True +) -> None: + tcol, ncol = _alloc_doc_split_column_names(str(col), set(result.columns)) + st_tipo: list[Optional[str]] = [] + st_num: list[Any] = [] + for v in result[col].tolist(): + tt, nn = split_doc_tipo_y_numero_texto(v, homologate=homologate_type) + st_tipo.append(tt) + st_num.append(nn if nn is not None else pd.NA) + result[tcol] = st_tipo + result[ncol] = st_num + log.append( + f"[OK] '{str(col)}' -> columnas '{tcol}' y '{ncol}' <- Dividir tipo y número (documento)" + ) + diff --git a/services/cleaning/domain/csv_cleaning/io.py b/services/cleaning/domain/csv_cleaning/io.py new file mode 100644 index 0000000..a62380f --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/io.py @@ -0,0 +1,132 @@ +"""Carga robusta de CSV (delimitador, encoding).""" +from __future__ import annotations + +import csv as _csv +from collections import Counter +from typing import Optional + +import pandas as pd + + +_CANDIDATE_DELIMITERS = ["|", ";", "\t", ",", ":"] + + +def detect_delimiter(filepath: str) -> str: + """Detecta el delimitador del CSV de forma robusta. + + Estrategia: + 1. Intentar `csv.Sniffer` con un sample grande. + 2. Si falla o el resultado parece incorrecto, contar ocurrencias por línea + de cada delimitador candidato y elegir el que produce el mayor número + de columnas estable entre líneas. + """ + encodings = ("utf-8", "utf-8-sig", "latin-1", "cp1252") + sample = "" + for enc in encodings: + try: + with open(filepath, "r", encoding=enc, errors="replace") as f: + sample = f.read(65536) + break + except Exception: + continue + + if not sample: + return "," + + try: + dialect = _csv.Sniffer().sniff(sample, delimiters="".join(_CANDIDATE_DELIMITERS)) + sniffed = dialect.delimiter + except _csv.Error: + sniffed = None + + lines = [ln for ln in sample.splitlines() if ln.strip()] + if not lines: + return sniffed or "," + + # Para cada candidato, calcular el número promedio de columnas y la + # consistencia (cuántas líneas tienen el mismo conteo dominante). + scores: dict[str, tuple[int, int]] = {} + for delim in _CANDIDATE_DELIMITERS: + counts = [ln.count(delim) for ln in lines[:50]] + if max(counts) == 0: + continue + # Modo (valor más frecuente) + common = Counter(counts).most_common(1)[0] + mode_count, mode_freq = common + if mode_count == 0: + continue + scores[delim] = (mode_count, mode_freq) + + if scores: + # Elegir el delimitador con mayor número de columnas (mode_count) y, + # como desempate, mayor consistencia (mode_freq). + best = max(scores.items(), key=lambda kv: (kv[1][0], kv[1][1])) + best_delim = best[0] + # Si el sniffer dio algo razonable y coincide, prevalece; si difiere y + # nuestro candidato es claramente mejor, usamos el nuestro. + if sniffed and sniffed in scores: + sniffed_score = scores[sniffed] + if sniffed_score[0] >= best[1][0] - 1: + return sniffed + return best_delim + + return sniffed or "," + + +def load_csv(filepath: str) -> tuple[pd.DataFrame, str]: + """Carga un CSV con detección de delimitador y tolerancia a filas malas. + + Devuelve `(df, delimiter)`. Lanza excepción si todos los intentos fallan. + """ + delimiter = detect_delimiter(filepath) + + encodings_to_try = ["utf-8", "utf-8-sig", "latin-1", "cp1252"] + last_error: Optional[Exception] = None + + for enc in encodings_to_try: + try: + df = pd.read_csv( + filepath, + delimiter=delimiter, + dtype=str, + encoding=enc, + engine="python", + on_bad_lines="skip", + ) + return df, delimiter + except Exception as exc: # noqa: BLE001 + last_error = exc + continue + + # Último intento con quoting permisivo y sin separador detectado + try: + df = pd.read_csv( + filepath, + sep=None, + dtype=str, + engine="python", + on_bad_lines="skip", + ) + return df, "(auto)" + except Exception as exc: # noqa: BLE001 + last_error = exc + + raise RuntimeError(f"No se pudo leer el CSV: {last_error}") + + +def load_csv_bytes(content: bytes, filename: str = "upload.csv") -> tuple[pd.DataFrame, str]: + """Carga un CSV desde bytes (upload HTTP) vía archivo temporal.""" + import os + import tempfile + + suffix = os.path.splitext(filename)[1] or ".csv" + with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: + tmp.write(content) + tmp_path = tmp.name + try: + return load_csv(tmp_path) + finally: + try: + os.unlink(tmp_path) + except OSError: + pass diff --git a/services/cleaning/domain/csv_cleaning/models.py b/services/cleaning/domain/csv_cleaning/models.py new file mode 100644 index 0000000..2940813 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/models.py @@ -0,0 +1,67 @@ +"""Tipos de columna y resumen de análisis.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Optional + +TYPE_INTEGER = "integer" +TYPE_DECIMAL = "decimal" +TYPE_DATE = "date" +TYPE_BOOLEAN = "boolean" +TYPE_CATEGORICAL = "categorical" +TYPE_TEXT = "text" +TYPE_MIXED = "mixed" +TYPE_EMPTY = "empty" + + +@dataclass +class ColumnSummary: + """Resumen inteligente para una columna del DataFrame.""" + name: str + inferred_type: str + example: str = "" + samples: list[str] = field(default_factory=list) + null_count: int = 0 + null_pct: float = 0.0 + unique_count: int = 0 + unique_pct: float = 0.0 + min: Optional[float] = None + max: Optional[float] = None + mean: Optional[float] = None + avg_length: Optional[float] = None + top_values: list[tuple[str, int]] = field(default_factory=list) + detected_date_format: Optional[str] = None + applicable_rules: list[str] = field(default_factory=list) + + def info_text(self) -> str: + """Texto humano con información adicional (para subtítulo de la UI).""" + parts: list[str] = [] + parts.append(f"nulos: {self.null_count} ({self.null_pct:.0f}%)") + parts.append(f"únicos: {self.unique_count}") + if self.inferred_type in (TYPE_INTEGER, TYPE_DECIMAL): + if self.min is not None: + parts.append(f"min: {_fmt_num(self.min)}") + if self.max is not None: + parts.append(f"max: {_fmt_num(self.max)}") + if self.mean is not None: + parts.append(f"media: {_fmt_num(self.mean)}") + elif self.inferred_type == TYPE_DATE: + if self.detected_date_format: + parts.append(f"formato: {self.detected_date_format}") + elif self.inferred_type in (TYPE_TEXT, TYPE_CATEGORICAL): + if self.avg_length is not None: + parts.append(f"long. prom: {self.avg_length:.1f}") + if self.top_values: + tops = ", ".join(f'"{v}" ({c})' for v, c in self.top_values[:3]) + parts.append(f"top: {tops}") + return " · ".join(parts) + + +def _fmt_num(x: float) -> str: + if x is None: + return "" + if isinstance(x, float) and x.is_integer(): + return str(int(x)) + if isinstance(x, float): + return f"{x:.2f}" + return str(x) diff --git a/services/cleaning/domain/csv_cleaning/notas.py b/services/cleaning/domain/csv_cleaning/notas.py new file mode 100644 index 0000000..84ad67a --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/notas.py @@ -0,0 +1,73 @@ +"""Extracción de documento y autorización desde notas de texto.""" +from __future__ import annotations + +import re +from typing import Any, Optional + +import pandas as pd + +from .nullish import _is_nullish_scalar + + +def _normalize_nota_text(raw: Any) -> str: + if _is_nullish_scalar(raw): + return "" + try: + if pd.isna(raw): + return "" + except TypeError: + pass + s = str(raw).strip() + if not s or s.lower() in ("nan", "none", "nat", ""): + return "" + return re.sub(r"\s+", " ", s) + + +def extract_paciente_documento_nota(raw: Any) -> Optional[str]: + """Número de documento tras ``Paciente:`` y antes del guión del nombre.""" + t = _normalize_nota_text(raw) + if not t: + return None + m = _PACIENTE_DOC_NOTA_RE.search(t) + return m.group(1) if m else None + + +def extract_autorizacion_nota(raw: Any) -> Optional[str]: + """Número tras ``Autorización No.`` (opcional por fila).""" + t = _normalize_nota_text(raw) + if not t: + return None + m = _AUTORIZACION_NOTA_RE.search(t) + return m.group(1) if m else None + + +def _alloc_nota_extract_column_names( + col_name: str, existing: set[str] +) -> tuple[str, str]: + """Nombres únicos para documento paciente y autorización.""" + base = re.sub(r"[^\w]+", "_", str(col_name).strip(), flags=re.ASCII) or "col" + for i in range(0, 200): + suf = f"_{i}" if i else "" + dcol, acol = f"{base}{suf}_nota_paciente_doc", f"{base}{suf}_nota_autorizacion" + if dcol not in existing and acol not in existing: + return dcol, acol + raise ValueError("No se pudieron asignar nombres de columnas únicos.") + + +def _apply_nota_extract_columns(result: pd.DataFrame, col: str, log: list[str]) -> None: + """Añade columnas con documento (Paciente:) y número de autorización si existe.""" + dcol, acol = _alloc_nota_extract_column_names(str(col), set(result.columns)) + docs: list[Any] = [] + auths: list[Any] = [] + for v in result[col].tolist(): + dd = extract_paciente_documento_nota(v) + aa = extract_autorizacion_nota(v) + docs.append(dd if dd is not None else pd.NA) + auths.append(aa if aa is not None else pd.NA) + result[dcol] = docs + result[acol] = auths + log.append( + f"[OK] '{str(col)}' -> columnas '{dcol}' y '{acol}' <- Extraer datos de notas" + ) + + diff --git a/services/cleaning/domain/csv_cleaning/nullish.py b/services/cleaning/domain/csv_cleaning/nullish.py new file mode 100644 index 0000000..65638a8 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/nullish.py @@ -0,0 +1,24 @@ +"""Detección de valores nulos / vacíos.""" +from __future__ import annotations + +from typing import Any + +import numpy as np +import pandas as pd + + +def _is_nullish_scalar(x: Any) -> bool: + if x is None or x is pd.NA: + return True + if isinstance(x, (bool, np.bool_)): + return False + if isinstance(x, (int, float, np.integer, np.floating)) and not isinstance( + x, (bool, np.bool_) + ): + if isinstance(x, float) and (np.isnan(x) or pd.isna(x)): + return True + return False + t = str(x).strip().lower() + if not t or t in ("nan", "none", "nat", "null", ""): + return True + return False diff --git a/services/cleaning/domain/csv_cleaning/numbers.py b/services/cleaning/domain/csv_cleaning/numbers.py new file mode 100644 index 0000000..e2051d7 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/numbers.py @@ -0,0 +1,258 @@ +"""Parsing numérico tolerante (miles, decimales, moneda).""" +from __future__ import annotations + +import re +from typing import Any, Literal, Optional + +import numpy as np +import pandas as pd + +from .nullish import _is_nullish_scalar + +_HINT_MAJORITY = 0.6 +_MIN_HINT_VOTES = 1 + + +def _strip_currency(s: str) -> str: + t = s.replace("\u00a0", " ").replace("\u202f", " ") + t = re.sub(r"[\s]+", "", t) + t = t.strip() + if not t or t.lower() in ("nan", "none", "nat"): + return "" + neg = False + if t[0:1] == "-": + neg, t = not neg, t[1:].lstrip() + if t[0:1] == "(" and t.endswith(")"): + neg, t = not neg, t[1:-1] + t = re.sub( + r"^[\$€\u00a3\u0e3f\uffe5\uffe6\uff04\ufe69]+", + "", + t, + count=1, + ) + t = re.sub(r"[\$€%]+$", "", t) + if not t and neg: + return "-0" + return ("-" if neg else "") + t + + +def _extract_numeric_chunk(s: str) -> str: + t = s.strip() + t = t.replace("$", "").replace("€", "").replace("\u00a3", "") + t = t.replace(" ", "") + cands1 = re.findall( + r"[+\-]?(?:\d{1,3}(?:[.,]\d{3})*|\d+)(?:[.,]\d+)?", t + ) + cands2 = re.findall( + r"[+\-]?\d[.,\d ]*\d", t + ) + cands = cands1 + cands2 + if not cands: + m = re.search(r"[\d.,\-]+", t) + if m: + c = m.group(0).replace("(", "-").replace(")", "") + return c.lstrip("+") + return "" + best = max(cands, key=len) if cands else "" + best = best.replace(" ", "").replace("(", "-").replace(")", "") + if not re.search(r"\d", best): + return "" + return best.lstrip("+") + + +def _as_float_s(s: str) -> Optional[float]: + if not s: + return None + try: + f = float(s) + if not np.isfinite(f) or abs(f) > 1e100: + return None + return f + except (ValueError, TypeError, OverflowError): + return None + + +def _parse_both_separators(body: str) -> Optional[float]: + """El separador más a la DERECHA entre `,` y `.` es el decimal; el otro, miles.""" + if "," not in body or "." not in body: + return None + t = body.strip() + if re.search(r"[^0-9,.\-]", t): + t2 = re.sub(r"[^0-9,.\-]", "", t) + t = t2 + if not t: + return None + neg = t[0:1] == "-" + b = t[1:].lstrip() if neg else t + if not b: + return None + i_comma, i_dot = b.rfind(","), b.rfind(".") + if min(i_comma, i_dot) < 0: + return None + dpos = max(i_comma, i_dot) + th = "," if b[dpos] == "." else "." + intp, fr = b[:dpos], b[dpos + 1 :] + if not re.fullmatch(r"\d{1,4}$", fr): + return None + intc = intp.replace(th, "") + if not intc.isdigit(): + return None + v = _as_float_s(f"{intc}.{fr}") + if v is None: + return None + if neg and v and v is not None: + return -abs(v) + return v + + +def _only_commas(w: str, dec_hint: Optional[Literal[",", "."]]) -> Optional[float]: + neg = w[0:1] == "-" + wn = w[1:].lstrip() if neg else w + if wn and re.search(r"[^0-9,]", wn): + wn = re.sub(r"[^0-9,]", "", wn) + n = wn.count(",") + if n == 0 and wn.isdigit(): + v = float(wn) + return -v if neg else v + if n == 0 or "." in wn: + return None + if n == 1: + i = wn.rfind(",") + h, t = wn[:i], wn[i + 1 :] + if not t.isdigit() or not h.isdigit(): + return None + if len(t) in (1, 2): + v = _as_float_s(f"{h}.{t}") + if v is None: + return None + return -v if neg else v + if len(t) == 3: + if dec_hint is None or dec_hint == ",": + v = _as_float_s(f"{h}{t}") + if v is None: + return None + return -v if neg else v + if dec_hint == ".": + v = _as_float_s(wn.replace(",", "")) + if v is None: + return None + return -v if neg else v + v = _as_float_s(wn.replace(",", "")) + if v is None: + return None + return -v if neg else v + + +def _only_dots(w: str, dec_hint: Optional[Literal[",", "."]]) -> Optional[float]: + neg = w[0:1] == "-" + wn = w[1:].lstrip() if neg else w + if wn and re.search(r"[^0-9.]", wn): + wn = re.sub(r"[^0-9.]", "", wn) + n = wn.count(".") + if n == 0 and wn.isdigit(): + v = float(wn) + return -v if neg else v + if n == 0 or "," in wn: + return None + if n == 1: + i = wn.rfind(".") + h, t = wn[:i], wn[i + 1 :] + if not t.isdigit() or not h.isdigit(): + return None + if len(t) in (1, 2): + v = _as_float_s(f"{h}.{t}") + if v is None: + return None + return -v if neg else v + if len(t) == 3: + if dec_hint is None or dec_hint == ".": + v = _as_float_s(f"{h}{t}") + if v is None: + return None + return -v if neg else v + if dec_hint == ",": + v = _as_float_s(wn.replace(".", "")) + if v is None: + return None + return -v if neg else v + v = _as_float_s(wn.replace(".", "")) + if v is None: + return None + return -v if neg else v + + +def _parse_lenient_one( + raw: Any, + decimal_hint: Optional[Literal[",", "."]] = None, +) -> float: + if _is_nullish_scalar(raw): + return float("nan") + t = _strip_currency(str(raw)) + if not t or t == "-": + return float("nan") + c = _extract_numeric_chunk(t) or t + c = c.strip() + if not c or c == "-": + return float("nan") + n_comma, n_dot = c.count(","), c.count(".") + if n_comma and n_dot: + f = _parse_both_separators(c) + elif n_comma and not n_dot: + f = _only_commas(c, decimal_hint) + elif n_dot and not n_comma: + f = _only_dots(c, decimal_hint) + else: + c2 = re.sub(r"[^0-9.\-]", "", c) + f = _as_float_s(c2) if c2 and re.fullmatch(r"-?\d+$", c2) else None + if f is not None and np.isfinite(f): + return f + return float("nan") + + +def _infer_decimal_hint_from_column( + series: pd.Series, +) -> Optional[Literal[",", "."]]: + """Voto con celdas que traen `,` y `.`: gana el de más a la derecha = decimal.""" + votes: list[Literal[",", "."]] = [] + for raw in series.dropna(): + if _is_nullish_scalar(raw): + continue + t = _strip_currency(str(raw)) + c = _extract_numeric_chunk(t) or t + c = c.strip() + if "," not in c or "." not in c or _parse_both_separators(c) is None: + continue + if c.rfind(",") > c.rfind("."): + votes.append(",") + else: + votes.append(".") + n = len(votes) + if n < _MIN_HINT_VOTES: + return None + a = sum(1 for v in votes if v == ",") + b = sum(1 for v in votes if v == ".") + if a + b == 0: + return None + if a / n >= _HINT_MAJORITY and a > b: + return "," + if b / n >= _HINT_MAJORITY and b > a: + return "." + return None + + +def _to_numeric_lenient( + series: pd.Series, + decimal_hint: Optional[Literal[",", "."]] = None, +) -> pd.Series: + """Convierte a flotante; pista de columna por voto (si `decimal_hint` es None).""" + hint = decimal_hint if decimal_hint is not None else _infer_decimal_hint_from_column( + series + ) + + def _cell(x: Any) -> float: + if _is_nullish_scalar(x): + return float("nan") + return _parse_lenient_one(x, hint) + + return series.map(_cell) + diff --git a/services/cleaning/domain/csv_cleaning/rules/__init__.py b/services/cleaning/domain/csv_cleaning/rules/__init__.py new file mode 100644 index 0000000..071ae2e --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/__init__.py @@ -0,0 +1,13 @@ +"""Reglas de limpieza registradas.""" +from __future__ import annotations + +from . import builtins as _builtins # noqa: F401 — registra reglas al importar +from .registry import CLEANING_RULES, get_rule, get_rule_fn, get_rule_name, register_rule + +__all__ = [ + "CLEANING_RULES", + "get_rule", + "get_rule_fn", + "get_rule_name", + "register_rule", +] diff --git a/services/cleaning/domain/csv_cleaning/rules/builtins.py b/services/cleaning/domain/csv_cleaning/rules/builtins.py new file mode 100644 index 0000000..2be0016 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/builtins.py @@ -0,0 +1,135 @@ +"""Registro de todas las reglas built-in. + +Añadir una regla nueva: implementa la función y llama a register_rule abajo. +""" +from __future__ import annotations + +from . import dates as date_rules +from . import documents as doc_rules +from . import numeric as num_rules +from . import special +from . import text as text_rules +from .registry import rebuild_cleaning_rules, register_rule + +register_rule( + "strip_special_spaces", + name="Eliminar caracteres especiales y espacios", + description=( + "Deja solo caracteres alfanuméricos (A-Z, a-z, 0-9). No usar antes de " + "«Normalizar fecha» en columnas con fecha y hora: pega cifras y puede impedir " + "interpretar el valor (aplique primero la normalización de fecha o use la regla " + "que conserva separadores)." + ), + fn=text_rules._rule_strip_special_spaces, +) +register_rule( + "strip_special_keep_date_seps", + name="Quitar símbolos; conserva /, -, : y espacios (fechas o texto con fechas)", + description=( + "Quita comas, $, comillas, etc. y deja letras, números, /, -, : (hora) y espacios. " + "Adecuado p. ej. en columna 'Fecha Servicio'." + ), + fn=text_rules._rule_strip_special_keep_date_seps, +) +register_rule( + "strip_spaces", + name="Eliminar únicamente espacios", + description="Elimina todos los caracteres de espacio en blanco.", + fn=text_rules._rule_strip_spaces, +) +register_rule( + "to_integer", + name="Convertir a Entero", + description=( + "Convierte a enteros con detección de miles/decimal (`,`/`.`) y moneda; " + "redondea decimales." + ), + fn=num_rules._rule_to_integer, +) +register_rule( + "date_to_ddmmyyyy", + name="Normalizar fecha → DD/MM/AAAA", + description=( + "Normaliza fechas numéricas, con separadores, con ruido o con meses en " + "texto (ej. 26-abril-2026) al formato DD/MM/AAAA." + ), + fn=date_rules._rule_date_to_ddmmyyyy, +) +register_rule( + "date_numeric_to_ddmmyyyy", + name="Conversión de formato numérico (Fechas) → DD/MM/AAAA", + description=( + "Detecta el formato numérico predominante (YYYYMMDD, DDMMYYYY, etc.) " + "y normaliza a DD/MM/AAAA." + ), + fn=date_rules._rule_date_numeric_to_ddmmyyyy, +) +register_rule( + "date_alpha_to_ddmmyyyy", + name="Conversión de formato alfanumérico (Fechas) → DD/MM/AAAA", + description=( + "Detecta el formato con separadores predominante " + "(DD/MM/YYYY, MM/DD/YYYY, YYYY/MM/DD, etc.) y normaliza a DD/MM/AAAA." + ), + fn=date_rules._rule_date_alpha_to_ddmmyyyy, +) +register_rule( + "date_messy_text_to_ddmmyyyy", + name="Texto a fecha (ruido en fechas) → DD/MM/AAAA", + description=( + "Quita ruido entre cifras (*, +, _, /, etc.), detecta el orden en la " + "columna (DDMMYYYY, YYYYMMDD, DDMMYY) y unifica a DD/MM/AAAA." + ), + fn=date_rules._rule_date_messy_text_to_ddmmyyyy, +) +register_rule( + "round_decimals_custom", + name="Aproximación Decimales (>0,5 sube, ≤0,5 baja)", + description=( + "Usa el mismo análisis de miles, decimales y moneda que «Entero». Tras leer " + "el número, si la parte fraccionaria de |valor| es mayor a 0,5 se aproxima al " + "entero hacia lejos de cero; en caso contrario, hacia cero. El 0,5 exacto se baja." + ), + fn=num_rules._rule_round_decimals_custom, +) +register_rule( + "doc_id_split_tipo_numero", + name="Dividir tipo y número (documento)", + description=( + "Añade dos columnas (nombres únicos p. ej. TipoID_doc_tipo, TipoID_doc_numeros): " + "prefijo con letras (NIT, CC, CE, …) y cifras sin separadores. No modifica la " + "columna de origen." + ), + fn=special._rule_passthrough_series, +) +register_rule( + "doc_type_homologate", + name="Homologar tipo de documento", + description=( + "Convierte tipos de documento escritos como nombre largo, sigla o texto con " + "símbolos a su abreviatura estándar (CC, TI, CE, RC, etc.)." + ), + fn=doc_rules._rule_doc_type_homologate, +) +register_rule( + "cop_mod_split", + name="Separa COP/MOD (copago / moderadora)", + description=( + "Según el año en la columna Fecha Servicio que elija en la tarjeta, clasifica " + "el importe en cuota moderadora (valores por año en Configuración) o copago. " + "Se ejecuta al final del proceso de limpieza para usar fechas ya normalizadas." + ), + fn=special._rule_passthrough_series, +) +register_rule( + "nota_paciente_autorizacion", + name="Extraer datos de notas (documento y autorización)", + description=( + "Desde texto tipo nota hospitalaria, extrae el número tras «Paciente:» (antes del " + "guión del nombre) y, si existe, el de «Autorización No.». Añade dos columnas; " + "no modifica el texto original." + ), + fn=special._rule_passthrough_series, +) + +rebuild_cleaning_rules() diff --git a/services/cleaning/domain/csv_cleaning/rules/dates.py b/services/cleaning/domain/csv_cleaning/rules/dates.py new file mode 100644 index 0000000..07d645c --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/dates.py @@ -0,0 +1,141 @@ +"""Reglas de normalización de fechas a DD/MM/AAAA.""" +from __future__ import annotations + +from typing import Any, Optional + +import pandas as pd + +from ..dates import ( + _detect_compact_date_formats, + _normalize_messy_date_to_compact, + _parse_any_date_to_tuple, + _parse_date_with_format, + _parse_date_with_text_month, + detect_date_format, +) +from ..nullish import _is_nullish_scalar + + +def _rule_date_to_ddmmyyyy(series: pd.Series) -> pd.Series: + """Normaliza fechas numéricas, alfanuméricas, con meses en texto y con ruido.""" + stripped = series.astype(str).str.replace(r"[\s\/\-\.]", "", regex=True) + stripped = stripped.where(series.notna(), other=pd.NA) + sep_fmt = detect_date_format(series) or detect_date_format(stripped) + compact8_fmt, compact6_fmt = _detect_compact_date_formats(series) + + def _fn(x: Any): + if pd.isna(x) or str(x).strip() == "": + return x + parsed = _parse_any_date_to_tuple( + x, + sep_fmt=sep_fmt, + compact8_fmt=compact8_fmt, + compact6_fmt=compact6_fmt, + ) + if parsed is None: + return x + d, m, y = parsed + return f"{d:02d}/{m:02d}/{y:04d}" + + return series.map(_fn) + + +def _rule_date_numeric_to_ddmmyyyy(series: pd.Series) -> pd.Series: + """Entrada: fechas en formato numérico (ej. 20240315 o 15032024). + + Salida: "DD/MM/AAAA" tras detectar el formato predominante. + """ + # Para esta regla consideramos los valores "numéricos" (quitamos separadores + # comunes si los hay para detectar un YYYYMMDD/DDMMYYYY confiable). + stripped = series.astype(str).str.replace(r"[\s\/\-\.]", "", regex=True) + stripped = stripped.where(series.notna(), other=pd.NA) + fmt = detect_date_format(stripped) + if fmt is None: + # fallback: intentar con detector normal + fmt = detect_date_format(series) + return _format_dates_to_ddmmyyyy(series, fmt) + + +def _rule_date_alpha_to_ddmmyyyy(series: pd.Series) -> pd.Series: + """Entrada: fechas en formato alfanumérico con separadores. + + Salida: "DD/MM/AAAA". + """ + fmt = detect_date_format(series) + return _format_dates_to_ddmmyyyy(series, fmt) + + +def _format_dates_to_ddmmyyyy(series: pd.Series, fmt: Optional[str]) -> pd.Series: + """Convierte cada valor al formato DD/MM/AAAA usando `fmt` como layout de + entrada detectado. La parte de hora (si existe) se descarta. Mantiene el + valor original si no puede parsear la fecha. + """ + if fmt is None: + return series.copy() + + def _fn(x): + if pd.isna(x) or str(x).strip() == "": + return x + raw = str(x) + date_part, _time_part = _split_date_time(raw) + target = date_part if date_part else raw + parsed = _parse_date_with_format(target, fmt) + if parsed is None: + return x + d, m, y = parsed + return f"{d:02d}/{m:02d}/{y:04d}" + + return series.map(_fn) + + +def _rule_date_messy_text_to_ddmmyyyy(series: pd.Series) -> pd.Series: + """Fechas con símbolos y basura entre cifras → DD/MM/AAAA. + + Normaliza a solo 6/8 dígitos, vota el formato con `detect_date_format` por + separado en compact8 y compact6; si el detector no devuelve nada, usa + `DDMMYYYY` / `DDMMYY` (alineado con la preferencia hispana en fechas + numéricas). + """ + cser = series.map(_normalize_messy_date_to_compact) + c8s = pd.Series( + [x for x in cser.tolist() if isinstance(x, str) and len(x) == 8] + ) + c6s = pd.Series( + [x for x in cser.tolist() if isinstance(x, str) and len(x) == 6] + ) + if c8s.empty and c6s.empty: + return series.copy() + + fmt8: Optional[str] = None + fmt6: Optional[str] = None + if not c8s.empty: + fmt8 = detect_date_format(c8s) or "DDMMYYYY" + if not c6s.empty: + fmt6 = detect_date_format(c6s) or "DDMMYY" + + def _one(x: Any, c: Any) -> Any: + if _is_nullish_scalar(x): + return x + if not isinstance(c, str) or len(c) not in (6, 8): + return x + f = (fmt8 or "DDMMYYYY") if len(c) == 8 else (fmt6 or "DDMMYY") + t = _parse_date_with_format(c, f) + if t is None and len(c) == 8: + t = _parse_date_with_format(c, "DDMMYYYY") or _parse_date_with_format( + c, "YYYYMMDD" + ) + if t is None and len(c) == 6: + t = _parse_date_with_format(c, "DDMMYY") + if t is None: + return x + d, m, y = t + return f"{d:02d}/{m:02d}/{y:04d}" + + return pd.Series( + [ + _one(v, cser.iloc[i] if i < len(cser) else None) for i, v in enumerate(series) + ], + index=series.index, + dtype=object, + ) + diff --git a/services/cleaning/domain/csv_cleaning/rules/documents.py b/services/cleaning/domain/csv_cleaning/rules/documents.py new file mode 100644 index 0000000..0fa3fc9 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/documents.py @@ -0,0 +1,23 @@ +"""Reglas de documento (homologación; split se aplica en apply.py).""" +from __future__ import annotations + +from typing import Any + +import pandas as pd + +from ..documents import homologate_doc_type, split_doc_tipo_y_numero_texto +from ..nullish import _is_nullish_scalar + + +def _rule_doc_type_homologate(series: pd.Series) -> pd.Series: + """Homologa una columna que contiene tipos de documento.""" + + def _fn(x: Any): + if _is_nullish_scalar(x): + return x + tipo, _num = split_doc_tipo_y_numero_texto(x, homologate=True) + if tipo is not None: + return tipo + return homologate_doc_type(x) + + return series.map(_fn) diff --git a/services/cleaning/domain/csv_cleaning/rules/numeric.py b/services/cleaning/domain/csv_cleaning/rules/numeric.py new file mode 100644 index 0000000..be5047e --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/numeric.py @@ -0,0 +1,57 @@ +"""Reglas numéricas (entero, redondeo).""" +from __future__ import annotations + +import math + +import numpy as np +import pandas as pd + +from ..nullish import _is_nullish_scalar +from ..numbers import _to_numeric_lenient + + +def _rule_to_integer(series: pd.Series) -> pd.Series: + """Convierte a entero (Int64 nullable) con parser tolerante.""" + numeric = _to_numeric_lenient(series.astype(str)) + return numeric.round().astype("Int64") + + +def _round_to_int_half_tie_le(x: float) -> int: + """Fracc. > 0,5 hacia arriba; 0,5 o menos hacia abajo. Banda 1e-9 en 0,5 (float). + + Misma noción que en la descripción de la regla. Trabaja sobre |x| y + recompone el signo. + """ + sign = -1 if x < 0 else 1 + if x == 0.0 or x == -0.0: + return 0 + ax = abs(float(x)) + ip = math.trunc(ax) + frac = ax - ip + if frac < 0.5 - 1e-9: + out = ip + elif frac > 0.5 + 1e-9: + out = ip + 1 + else: + out = ip + return -out if sign < 0 else out + + +def _rule_round_decimals_custom(series: pd.Series) -> pd.Series: + """Mismo parser tolerante que convertir a entero; aprox. con fracc. >0,5 arriba.""" + numeric = _to_numeric_lenient(series.astype(str)) + + def _fn(x): + if _is_nullish_scalar(x) or (isinstance(x, (float, np.floating)) and (not np.isfinite(x) or np.isnan(x))): + return pd.NA + try: + f = float(x) + except (TypeError, ValueError, OverflowError): + return pd.NA + if not np.isfinite(f): + return pd.NA + if f == 0.0 or f == -0.0: + return 0 + return _round_to_int_half_tie_le(f) + + return numeric.map(_fn).astype("Int64") diff --git a/services/cleaning/domain/csv_cleaning/rules/registry.py b/services/cleaning/domain/csv_cleaning/rules/registry.py new file mode 100644 index 0000000..d0ad72a --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/registry.py @@ -0,0 +1,80 @@ +"""Registro de reglas de limpieza. + +Para agregar una regla nueva: +1. Implementa ``fn(series: pd.Series) -> pd.Series`` (o passthrough si es especial). +2. Llama a ``register_rule(...)`` en ``rules/builtins.py``. +3. Si la regla es especial (añade columnas, usa config), maneja el id en ``apply.py``. +4. Incluye el id en ``_applicable_rules_for_type`` (type_inference.py) si debe sugerirse. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Callable, Optional + +import pandas as pd + +RuleFn = Callable[[pd.Series], pd.Series] + + +@dataclass(frozen=True) +class RuleSpec: + id: str + name: str + description: str + fn: RuleFn + + def as_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "description": self.description, + "fn": self.fn, + } + + +_REGISTRY: dict[str, RuleSpec] = {} +_ORDER: list[str] = [] + + +def register_rule( + rule_id: str, + *, + name: str, + description: str, + fn: RuleFn, +) -> RuleSpec: + """Registra una regla. Si el id ya existe, lo reemplaza manteniendo el orden.""" + spec = RuleSpec(id=rule_id, name=name, description=description, fn=fn) + if rule_id not in _REGISTRY: + _ORDER.append(rule_id) + _REGISTRY[rule_id] = spec + return spec + + +def get_rule(rule_id: str) -> Optional[dict[str, Any]]: + spec = _REGISTRY.get(rule_id) + return spec.as_dict() if spec else None + + +def get_rule_fn(rule_id: str) -> Optional[RuleFn]: + spec = _REGISTRY.get(rule_id) + return spec.fn if spec else None + + +def get_rule_name(rule_id: str) -> str: + spec = _REGISTRY.get(rule_id) + return spec.name if spec else rule_id + + +def list_rules() -> list[dict[str, Any]]: + return [_REGISTRY[rid].as_dict() for rid in _ORDER if rid in _REGISTRY] + + +# Compat: lista mutable usada por la API (se rellena al importar builtins). +CLEANING_RULES: list[dict[str, Any]] = [] + + +def rebuild_cleaning_rules() -> list[dict[str, Any]]: + CLEANING_RULES.clear() + CLEANING_RULES.extend(list_rules()) + return CLEANING_RULES diff --git a/services/cleaning/domain/csv_cleaning/rules/special.py b/services/cleaning/domain/csv_cleaning/rules/special.py new file mode 100644 index 0000000..04c1521 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/special.py @@ -0,0 +1,8 @@ +"""Marcadores passthrough para reglas que no transforman la serie in-place.""" +from __future__ import annotations + +import pandas as pd + + +def _rule_passthrough_series(s: pd.Series) -> pd.Series: + return s.copy() diff --git a/services/cleaning/domain/csv_cleaning/rules/text.py b/services/cleaning/domain/csv_cleaning/rules/text.py new file mode 100644 index 0000000..66e6777 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/rules/text.py @@ -0,0 +1,40 @@ +"""Reglas de limpieza de texto / espacios.""" +from __future__ import annotations + +import re + +import pandas as pd + + +def _rule_strip_special_spaces(series: pd.Series) -> pd.Series: + """Elimina todo lo que no sea alfanumérico (incluye espacios).""" + pattern = re.compile(r"[^A-Za-z0-9]", flags=re.UNICODE) + + def _fn(x): + if pd.isna(x): + return x + return pattern.sub("", str(x)) + + return series.map(_fn) + + +def _rule_strip_special_keep_date_seps(series: pd.Series) -> pd.Series: + """Conserva alfanuméricos, separadores de fecha '/', '-' y hora ':', y espacio.""" + pattern = re.compile(r"[^A-Za-z0-9\/\-: ]", flags=re.UNICODE) + + def _fn(x): + if pd.isna(x): + return x + return pattern.sub("", str(x)) + + return series.map(_fn) + + +def _rule_strip_spaces(series: pd.Series) -> pd.Series: + """Elimina únicamente espacios (incluidos tabs y saltos de línea).""" + def _fn(x): + if pd.isna(x): + return x + return re.sub(r"\s+", "", str(x)) + + return series.map(_fn) diff --git a/services/cleaning/domain/csv_cleaning/type_inference.py b/services/cleaning/domain/csv_cleaning/type_inference.py new file mode 100644 index 0000000..d85d219 --- /dev/null +++ b/services/cleaning/domain/csv_cleaning/type_inference.py @@ -0,0 +1,143 @@ +"""Inferencia de tipo de columna y reglas aplicables.""" +from __future__ import annotations + +from typing import Optional + +import pandas as pd + +from .column_hints import ( + _column_name_suggests_code_id, + _column_name_suggests_monetary_value, + _column_name_suggests_quantity_or_number_not_bool, + _content_looks_like_doc_id_letter_prefix_column, + _raw_strings_suggest_alnum_mixed_presentation, + _raw_strings_suggest_monetary_presentation, +) +from .dates import _normalize_messy_date_to_compact, detect_date_format +from .models import ( + TYPE_BOOLEAN, + TYPE_CATEGORICAL, + TYPE_DATE, + TYPE_DECIMAL, + TYPE_EMPTY, + TYPE_INTEGER, + TYPE_MIXED, + TYPE_TEXT, +) +from .numbers import _to_numeric_lenient + +_BOOLEAN_VALUES = {"true", "false", "si", "sí", "no", "1", "0", "yes"} + + +def _detect_messy_text_date_format_guess(non_null: pd.Series) -> Optional[str]: + """Fechas con ruido: compacta a dígitos; si hay consenso, reutiliza el detector. + (Depende de `_normalize_messy_date_to_compact`, definida más abajo en el módulo.) + """ + # resolución en tiempo de ejecución + norm = non_null.map(_normalize_messy_date_to_compact) # type: ignore[name-defined] + compacts: list[str] = [] + for c in norm: + if isinstance(c, str) and len(c) in (6, 8): + compacts.append(c) + n = max(len(non_null), 1) + if not compacts or len(compacts) < max(2, int(0.25 * n)): + return None + s_compact = pd.Series(compacts, dtype=object) + return detect_date_format(s_compact) + + +def _infer_column_type( + series: pd.Series, column_name: str = "" +) -> tuple[str, Optional[str]]: + """Devuelve (tipo, formato_fecha detectado; solo si aplica a tipo fecha).""" + non_null = series.dropna().astype(str).str.strip() + non_null = non_null[non_null != ""] + if non_null.empty: + return TYPE_EMPTY, None + + cname = str(column_name or "") + + # Boolean: no aplicar 0/1/ sí-no a columnas cuyo nombre pide importe, cantid., fecha… + if not _column_name_suggests_quantity_or_number_not_bool(cname): + lowered = non_null.str.lower() + if lowered.isin(_BOOLEAN_VALUES).all() and lowered.nunique() <= 3: + return TYPE_BOOLEAN, None + + is_money = _column_name_suggests_monetary_value(cname) or _raw_strings_suggest_monetary_presentation( + non_null + ) + is_code = _column_name_suggests_code_id(cname) + is_doc_pattern = _content_looks_like_doc_id_letter_prefix_column(non_null) + + # Fecha: no forzar en importe/código, ni con datos tipo `CC`+cifra (6–8 dígitos sueltos) + # ni cuando el contenido parece moneda (p. ej. 113,400.00 → no es DDMMYYYY). + if not is_money and not is_code and not is_doc_pattern: + date_fmt = detect_date_format(non_null) + if date_fmt: + return (TYPE_DATE, date_fmt) + messy_date_fmt = _detect_messy_text_date_format_guess(non_null) + if messy_date_fmt: + return (TYPE_DATE, messy_date_fmt) + + numeric_candidate = _to_numeric_lenient(non_null, decimal_hint=None) + numeric_ratio = numeric_candidate.notna().mean() + if numeric_ratio >= 0.9: + if _raw_strings_suggest_alnum_mixed_presentation(non_null): + return (TYPE_MIXED, None) + is_all_integer = (numeric_candidate.dropna() % 1 == 0).all() + if is_all_integer and not _raw_strings_suggest_monetary_presentation(non_null): + return (TYPE_INTEGER, None) + return (TYPE_DECIMAL, None) + + unique_ratio = non_null.nunique() / len(non_null) + if non_null.nunique() <= 15 and unique_ratio < 0.3: + return TYPE_CATEGORICAL, None + if 0.1 < numeric_ratio < 0.9: + return TYPE_MIXED, None + return TYPE_TEXT, None + + +def _applicable_rules_for_type(inferred_type: str) -> list[str]: + """Identificadores de reglas a mostrar según el tipo inferido (sin redundancias).""" + base = ["strip_special_spaces", "strip_special_keep_date_seps", "strip_spaces"] + if inferred_type == TYPE_INTEGER: + return base + ["cop_mod_split"] + if inferred_type == TYPE_DECIMAL: + return base + [ + "to_integer", + "round_decimals_custom", + "doc_id_split_tipo_numero", + "doc_type_homologate", + "cop_mod_split", + ] + if inferred_type == TYPE_DATE: + return base + [ + "doc_id_split_tipo_numero", + "doc_type_homologate", + "date_to_ddmmyyyy", + "nota_paciente_autorizacion", + ] + if inferred_type == TYPE_MIXED: + return base + [ + "to_integer", + "round_decimals_custom", + "doc_id_split_tipo_numero", + "doc_type_homologate", + "date_to_ddmmyyyy", + "cop_mod_split", + "nota_paciente_autorizacion", + ] + # Texto / categórico / boolean / empty → todas las de limpieza de texto + las + # conversoras (el usuario sabrá si aplica). + return base + [ + "to_integer", + "round_decimals_custom", + "doc_id_split_tipo_numero", + "doc_type_homologate", + "date_to_ddmmyyyy", + "cop_mod_split", + "nota_paciente_autorizacion", + ] + + +applicable_rule_ids_for_type = _applicable_rules_for_type diff --git a/services/cleaning/infrastructure/database.py b/services/cleaning/infrastructure/database.py index 697580e..e32fa0e 100644 --- a/services/cleaning/infrastructure/database.py +++ b/services/cleaning/infrastructure/database.py @@ -54,10 +54,26 @@ class ExtractionBatch(Base): updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) +class Job(Base): + """Contenedor de trabajo del usuario (lectura/validación de FK).""" + __tablename__ = "jobs" + + id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) + status: Mapped[str] = mapped_column(String(32), default="pending", index=True) + title: Mapped[str | None] = mapped_column(Text, nullable=True) + job_metadata: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default=dict) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) + + class Task(Base): + """Módulo/resultado dentro de un Job; un job puede tener N tasks (una por servicio).""" __tablename__ = "tasks" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) + job_id: Mapped[str | None] = mapped_column( + String(36), ForeignKey("jobs.id"), nullable=True, index=True + ) type: Mapped[str] = mapped_column(String(64), index=True) status: Mapped[str] = mapped_column(String(32), default="pending", index=True) title: Mapped[str | None] = mapped_column(Text, nullable=True) @@ -88,7 +104,13 @@ class CleaningJob(Base): __tablename__ = "cleaning_jobs" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) - extraction_batch_id: Mapped[str] = mapped_column(ForeignKey("extraction_batches.id"), index=True) + extraction_batch_id: Mapped[str | None] = mapped_column( + ForeignKey("extraction_batches.id"), nullable=True, index=True + ) + job_id: Mapped[str | None] = mapped_column( + String(36), ForeignKey("jobs.id"), nullable=True, index=True + ) + source: Mapped[str] = mapped_column(String(32), default="extraction", index=True) status: Mapped[str] = mapped_column(String(32), default="pending", index=True) request: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) log: Mapped[list[str]] = mapped_column(JSON, default=list) @@ -102,7 +124,9 @@ class CleanedRow(Base): id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) cleaning_job_id: Mapped[str] = mapped_column(ForeignKey("cleaning_jobs.id"), index=True) - extraction_batch_id: Mapped[str] = mapped_column(ForeignKey("extraction_batches.id"), index=True) + extraction_batch_id: Mapped[str | None] = mapped_column( + ForeignKey("extraction_batches.id"), nullable=True, index=True + ) row_index: Mapped[int] = mapped_column(Integer, default=0) row_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) diff --git a/services/cleaning/requirements.txt b/services/cleaning/requirements.txt index 4c07d5e..e8835ed 100644 --- a/services/cleaning/requirements.txt +++ b/services/cleaning/requirements.txt @@ -3,5 +3,6 @@ numpy==2.2.6 pandas==2.2.3 psycopg[binary]==3.2.3 pydantic==2.11.9 +python-multipart==0.0.20 SQLAlchemy==2.0.36 uvicorn[standard]==0.32.1 diff --git a/services/db/alembic/versions/0003_cleaning_csv_and_job_id.py b/services/db/alembic/versions/0003_cleaning_csv_and_job_id.py new file mode 100644 index 0000000..d592c6c --- /dev/null +++ b/services/db/alembic/versions/0003_cleaning_csv_and_job_id.py @@ -0,0 +1,90 @@ +"""cleaning_jobs: batch_id nullable, job_id, source; cleaned_rows.batch_id nullable. + +Revision ID: 0003 +Revises: 0002 +Create Date: 2026-07-10 + +""" +from __future__ import annotations + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "0003" +down_revision: Union[str, None] = "0002" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def _col_exists(conn: sa.engine.Connection, table: str, column: str) -> bool: + inspector = sa.inspect(conn) + return any(c["name"] == column for c in inspector.get_columns(table)) + + +def upgrade() -> None: + conn = op.get_bind() + + if _col_exists(conn, "cleaning_jobs", "extraction_batch_id"): + op.alter_column( + "cleaning_jobs", + "extraction_batch_id", + existing_type=sa.String(36), + nullable=True, + ) + + if not _col_exists(conn, "cleaning_jobs", "job_id"): + op.add_column( + "cleaning_jobs", + sa.Column("job_id", sa.String(36), sa.ForeignKey("jobs.id"), nullable=True), + ) + op.create_index("ix_cleaning_jobs_job_id", "cleaning_jobs", ["job_id"]) + + if not _col_exists(conn, "cleaning_jobs", "source"): + op.add_column( + "cleaning_jobs", + sa.Column( + "source", + sa.String(32), + nullable=False, + server_default="extraction", + ), + ) + op.create_index("ix_cleaning_jobs_source", "cleaning_jobs", ["source"]) + + if _col_exists(conn, "cleaned_rows", "extraction_batch_id"): + op.alter_column( + "cleaned_rows", + "extraction_batch_id", + existing_type=sa.String(36), + nullable=True, + ) + + +def downgrade() -> None: + conn = op.get_bind() + + if _col_exists(conn, "cleaned_rows", "extraction_batch_id"): + op.alter_column( + "cleaned_rows", + "extraction_batch_id", + existing_type=sa.String(36), + nullable=False, + ) + + if _col_exists(conn, "cleaning_jobs", "source"): + op.drop_index("ix_cleaning_jobs_source", table_name="cleaning_jobs") + op.drop_column("cleaning_jobs", "source") + + if _col_exists(conn, "cleaning_jobs", "job_id"): + op.drop_index("ix_cleaning_jobs_job_id", table_name="cleaning_jobs") + op.drop_column("cleaning_jobs", "job_id") + + if _col_exists(conn, "cleaning_jobs", "extraction_batch_id"): + op.alter_column( + "cleaning_jobs", + "extraction_batch_id", + existing_type=sa.String(36), + nullable=False, + ) diff --git a/services/desktop_app/.env.minio.example b/services/desktop_app/.env.minio.example new file mode 100644 index 0000000..66b6d97 --- /dev/null +++ b/services/desktop_app/.env.minio.example @@ -0,0 +1,11 @@ +# Credenciales locales para publicar en MinIO (NO subir al git). +# Copia este archivo a .env.minio y rellena los valores. + +MINIO_ENDPOINT=https://s3.builker.xyz +MINIO_BUCKET=apps-versions +MINIO_PREFIX=IPSExtractor +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= + +# Opcional (DD/MM/YYYY). Si se omite, usa version.json o la fecha de hoy. +# RELEASE_DATE=06/07/2026 diff --git a/services/desktop_app/README.md b/services/desktop_app/README.md index c03ce34..32b5a8f 100644 --- a/services/desktop_app/README.md +++ b/services/desktop_app/README.md @@ -106,17 +106,59 @@ npm run dev ## Build para producción ```bash -npm run build # compila el renderer (TypeScript + Vite) -npm run dist # genera el instalador Windows NSIS +# 1) Sube la versión en package.json (ej. 1.0.1) y la fecha en src/shared/version.json +# 2) Configura credenciales una vez: copia .env.minio.example → .env.minio +# 3) Genera el instalador y súbelo a MinIO automáticamente: +npm run dist ``` -El instalador se crea en `release/IPSExtractor-Desktop-1.0.0-Setup.exe`. +El instalador queda en `release/` y se publica en: + +`s3.builker.xyz/apps-versions/IPSExtractor/{version}/` + +Si solo quieres el `.exe` local **sin** subir a MinIO: + +```bash +npm run dist:local +``` ### Versión de la app | Campo | Archivo | |-------|---------| | Número (`1.0.0`) | `package.json` → `version` | -| Fecha de release | `src/shared/version.json` → `releaseDate` | +| Fecha de release | `src/shared/version.json` → `releaseDate` (ISO) | -La versión se muestra en el menú lateral y en la esquina superior derecha. Al publicar una nueva versión, actualiza ambos archivos y ejecuta `npm run dist`. +La versión se muestra en el menú lateral y en la esquina superior derecha. + +### Auto-update (MinIO) + +Al iniciar la app **instalada**, consulta: + +`https://s3.builker.xyz/apps-versions/IPSExtractor/latest.json` + +Si hay una versión mayor, pregunta al usuario, descarga el Setup.exe, verifica `sha256` y lanza el instalador NSIS. + +Layout en el bucket `apps-versions`: + +```text +IPSExtractor/ + latest.json + 1.0.1/IPSExtractor-Desktop-1.0.1-Setup.exe + 1.0.1/DATA.txt ← fecha DD/MM/YYYY, ej. 06/07/2026 +``` + +**Requisito:** el prefijo `IPSExtractor/` debe permitir `GetObject` anónimo en la API (`s3.builker.xyz`). La política se configura en la consola (`minio.builker.xyz`). + +Credenciales de publicación (solo en tu PC): + +```bash +cp .env.minio.example .env.minio +# Edita MINIO_ACCESS_KEY y MINIO_SECRET_KEY +``` + +`npm run dist` las usa automáticamente al final. También puedes republicar sin rebuild: + +```bash +npm run publish:minio +``` diff --git a/services/desktop_app/electron/autoUpdate.cjs b/services/desktop_app/electron/autoUpdate.cjs new file mode 100644 index 0000000..bfc79f6 --- /dev/null +++ b/services/desktop_app/electron/autoUpdate.cjs @@ -0,0 +1,224 @@ +/** + * Auto-update desde MinIO (apps-versions/IPSExtractor). + * Sin credenciales: lee latest.json y el Setup.exe con GetObject público. + */ +const { app } = require("electron"); +const crypto = require("node:crypto"); +const fs = require("node:fs"); +const fsp = require("node:fs/promises"); +const http = require("node:http"); +const https = require("node:https"); +const os = require("node:os"); +const path = require("node:path"); +const { spawn } = require("node:child_process"); +const { URL } = require("node:url"); + +const { version: CURRENT_VERSION } = require("../package.json"); + +const LATEST_JSON_URL = + process.env.IPS_UPDATE_LATEST_URL || + "https://s3.builker.xyz/apps-versions/IPSExtractor/latest.json"; + +const FETCH_TIMEOUT_MS = 12_000; + +/** + * @typedef {{ + * version: string; + * releaseDate?: string; + * releaseDateDisplay?: string; + * fileName: string; + * url: string; + * sha256: string; + * size?: number; + * }} UpdateManifest + */ + +/** + * @param {string} a + * @param {string} b + * @returns {number} -1 if ab + */ +function compareSemver(a, b) { + const pa = String(a).replace(/^v/i, "").split(".").map((x) => Number.parseInt(x, 10) || 0); + const pb = String(b).replace(/^v/i, "").split(".").map((x) => Number.parseInt(x, 10) || 0); + const len = Math.max(pa.length, pb.length); + for (let i = 0; i < len; i++) { + const x = pa[i] ?? 0; + const y = pb[i] ?? 0; + if (x < y) return -1; + if (x > y) return 1; + } + return 0; +} + +/** + * @param {string} urlStr + * @param {{ onProgress?: (received: number, total: number) => void, destPath?: string }} [opts] + * @returns {Promise<{ buffer?: Buffer, destPath?: string, headers: import('http').IncomingHttpHeaders }>} + */ +function download(urlStr, opts = {}) { + return new Promise((resolve, reject) => { + const url = new URL(urlStr); + const lib = url.protocol === "http:" ? http : https; + const req = lib.get( + url, + { + timeout: FETCH_TIMEOUT_MS, + headers: { "User-Agent": `IPSExtractor-Desktop/${CURRENT_VERSION}` }, + }, + (res) => { + if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + res.resume(); + download(res.headers.location, opts).then(resolve, reject); + return; + } + if (res.statusCode !== 200) { + res.resume(); + reject(new Error(`HTTP ${res.statusCode} al descargar ${urlStr}`)); + return; + } + const total = Number(res.headers["content-length"] || 0); + let received = 0; + + if (opts.destPath) { + const out = fs.createWriteStream(opts.destPath); + res.on("data", (chunk) => { + received += chunk.length; + opts.onProgress?.(received, total); + }); + res.pipe(out); + out.on("finish", () => resolve({ destPath: opts.destPath, headers: res.headers })); + out.on("error", reject); + res.on("error", reject); + return; + } + + const chunks = []; + res.on("data", (chunk) => { + chunks.push(chunk); + received += chunk.length; + opts.onProgress?.(received, total); + }); + res.on("end", () => resolve({ buffer: Buffer.concat(chunks), headers: res.headers })); + res.on("error", reject); + }, + ); + req.on("timeout", () => { + req.destroy(); + reject(new Error("Timeout al contactar el servidor de versiones")); + }); + req.on("error", reject); + }); +} + +/** + * @returns {Promise<{ updateAvailable: boolean, currentVersion: string, manifest?: UpdateManifest, error?: string }>} + */ +async function checkForUpdates() { + try { + const { buffer } = await download(LATEST_JSON_URL); + if (!buffer) { + return { updateAvailable: false, currentVersion: CURRENT_VERSION, error: "Respuesta vacía" }; + } + /** @type {UpdateManifest} */ + const manifest = JSON.parse(buffer.toString("utf8")); + if (!manifest?.version || !manifest?.url || !manifest?.fileName) { + return { + updateAvailable: false, + currentVersion: CURRENT_VERSION, + error: "Manifiesto inválido", + }; + } + + // Si falta la fecha de DATA.txt en el manifiesto, intentar leerla junto al exe + if (!manifest.releaseDateDisplay) { + try { + const dataUrl = manifest.url.replace(/[^/]+$/, "DATA.txt"); + const { buffer: dataBuf } = await download(dataUrl); + if (dataBuf) { + const display = dataBuf.toString("utf8").trim().split(/\r?\n/)[0]?.trim(); + if (display) { + manifest.releaseDateDisplay = display; + const iso = display.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); + if (iso && !manifest.releaseDate) { + manifest.releaseDate = `${iso[3]}-${iso[2].padStart(2, "0")}-${iso[1].padStart(2, "0")}`; + } + } + } + } catch { + /* opcional */ + } + } + + const newer = compareSemver(CURRENT_VERSION, manifest.version) < 0; + return { + updateAvailable: newer, + currentVersion: CURRENT_VERSION, + manifest, + }; + } catch (err) { + return { + updateAvailable: false, + currentVersion: CURRENT_VERSION, + error: err instanceof Error ? err.message : String(err), + }; + } +} + +/** + * @param {UpdateManifest} manifest + * @param {(payload: { phase: string, received?: number, total?: number, percent?: number }) => void} [onProgress] + */ +async function downloadAndInstall(manifest, onProgress) { + if (!manifest?.url || !manifest?.fileName) { + throw new Error("Manifiesto incompleto"); + } + const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "ipsextractor-update-")); + const destPath = path.join(tmpDir, path.basename(manifest.fileName)); + + onProgress?.({ phase: "downloading", received: 0, total: manifest.size || 0, percent: 0 }); + + await download(manifest.url, { + destPath, + onProgress: (received, total) => { + const t = total || manifest.size || 0; + const percent = t > 0 ? Math.min(100, Math.round((received / t) * 100)) : 0; + onProgress?.({ phase: "downloading", received, total: t, percent }); + }, + }); + + onProgress?.({ phase: "verifying" }); + if (manifest.sha256) { + const hash = crypto.createHash("sha256"); + const fileBuf = await fsp.readFile(destPath); + hash.update(fileBuf); + const digest = hash.digest("hex").toLowerCase(); + if (digest !== String(manifest.sha256).toLowerCase()) { + await fsp.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined); + throw new Error("El instalador descargado no coincide con el hash esperado (sha256)."); + } + } + + onProgress?.({ phase: "installing" }); + // NSIS silent install; la app debe cerrarse para que el instalador reemplace archivos. + const child = spawn(destPath, ["/S"], { + detached: true, + stdio: "ignore", + windowsHide: false, + }); + child.unref(); + + setTimeout(() => { + app.quit(); + }, 400); + + return { ok: true, destPath }; +} + +module.exports = { + CURRENT_VERSION, + LATEST_JSON_URL, + compareSemver, + checkForUpdates, + downloadAndInstall, +}; diff --git a/services/desktop_app/electron/icon.png b/services/desktop_app/electron/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..54863e5c6833e4bfba6f8dab2527a1ee4e4bd230 GIT binary patch literal 56966 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ºvs6d)$Y9bMiB^#xo8b&fnGuXh_qoLjgDy?D#MnV(;79}vK z&Wqnin#q`PRhYnK)eqnn$} z$0;ltg-mjH@6SG1Yyb7v@Ahnd$wAy<$xfp0VlgHop4Ho5xUlVPcEco3PZNSiKDL$ zLbM>Z5;yXy@&vTN-P?=C!=U+NI=#0|4hua6|(Yie@ zftQZsM)Ri!BlmNiD3j7hFJjtYgspi3h)pfIWhYlxzi{!L?H}BFO-Kc~KJ^5QywC=q zAh&G<{-fV%HbpZ^FALM$a`X-{Zoi~CCx9I|r(aYd( ze19K)a=8xQzS@KY<92LlH{}lH`YXngV&bn+N-1Cx_2GfYA;bCIQYZPinJa*CJ(7yH zDjc{MsJ;O_cbF5Xx`FZnI<+Q5SqceHAwe2)HK;U&Xwp~s02N9p8YRUjL|kc!o}Hds z{`&bl!S9c|lS$Mmr90-%D+g)yZ)`Rji?EF@vDQd&njb(js6v=V5GJu=9`3~37=i|L%kP?MalZ=q&$}hehB` z>UhrnHSH^4dCaHCGQ2c}L5S zsQjErnAij^Y>we~k_`UYTZ^z;a};`T+Tw_AGxGc!emw0TVQ9c1jfb$U5`wyPhkODm zIcPE#3SS_kA{y0{22k1Y_03l8Y$F^rte`NiWkG_Xl#+nI)Ug{o{q4bIvU(Z{oZwo0TH<;B zid5^sM~8C5RdLtWC=Q=`~{LtcjV1?72$J+{r0wJ9u5*xsz#hb=~O8WjX<7jDDLt7 zDTOemoFM@si$Zmn!C(L0F1)%mLY_Va(Kq9G3~*>x^(#&a20T$bf{oz;-0SbdW`7@c z1_!X+KZM=kAsmeR(4CB69LK5*h{gaz0pu3Mp(Hr35QmhsD>YcD)D%TIWzKwl!&DnE zKj9Ed9@gpk^*1lw&{cvtTIAWOB1DdoWT}34v`x}1E0*@+a0(a$dR|G(?RXa#1p|^l zDSUfTS6`K;s>41;=`Zk7a0z^{H@icn_png5+$U92(go5e&yeDE+19BTfe)# z-AbZZul$cWXXbB6wikc#%=&x3F3D;=7ppkDPW9av)2fd?rdjvd1DkG;s@t+%V5s<5 zX2UGIWtwjcPzUM1v8xX=^G7wK+?806<4-eb5>y{Piv|435BA^-x4T3TL2w0E2nN#> zqI}-cnp&?RzM*9kOm96YAR9P z3!vfGpcB;8{6g0Ybg9ua5>0nnVUMw_V7c4py$hXo>wXdUA3gVlLEgB2@1@0%-p4^e zvy#m0*coMFoMnOa%4$??)ccyk;6$VH!pO5=0F+d!wY<9c+CU>spZ-Nr8GlOorB3VC zS5n@BoVSRjnD(X%UFjh(AFy5ZZ~gQAgK8Qo<$wIjZqm7($$-s!D)H3t<&L&KbbbWjMLWufk$bg_d6dY88WaOk1)Vm1e3EKp?9+K7VN; zcbGMP(v6V|?uhsIaz~IzT`c1g6>tZnPjRUw_h@>_yRa}|gruh-z%wFIGH!15?2GM% zm3xn~^dDCY&PT4bn;X9#)D|4VmKA<~<`vH#=n?RYZo1Zuf40A03rFL%pMmUCy#uae z_kMkKW#dbnoFft%#Ih8Ks8IgmF*O3_g<0&6KpGbu#{=8(!FEvjJ-zy)0np#1N@r@- z=Q-pen=%GJzKVcKJfF~1V5N)`3rM_Ob#DKY$vEl$+5KecdiSoNGQpNy9QcWC#E8~Y z2WWsp#ZV1th(P0pD}&Dpzw|@vbk_#zVDhhMc@?VEhXX2*@1F~dv1iJE^hPh@{$k}f z)4Tx#Uq~~OdiH}K$b9qn_7CcNgVEV;IKFuJ7dO|MKiR*{^DIODe4JKPBYJQd+~whg z)z>cFS8Qf4uskzW0MBJKBj6;YS#5UK4|cZhH_OcNpGXMdzrNTxsE?B?(s8D6+d`C* z;A)d|IPk3N|HZ~uJBgE}CuNBXrEAoy4}ZJWmP>JdUWL0z7IGxsFCRsDW0)JD%m$`h zen%+*#`-GJr`&x5?#Vd_tl8(8QYNkdIJ1=BNoNRG`lD{Y;>ia|294n6KR-C|_s5en zAf#)QNhX3f_z&;cH4q;R-&lOzrc7r@R6am=4H{;0^Th;Yp2A?#hX;yNu%HSn2t{-k zfpEO(`9d56Rtd1B7!!j{FMzgJfuR?`71HJ42&B=X-|^0NCeiIp^h=J= zcI!PtWW!|hpxzoSZ*4Vc%R_?EsjpSkI|)Hnhh)9dwrx92v&@GVRGtM7KnbPNbzBmL z8EvNQJo8<5S8>D6I&eR-s2iIQ{>_W$-&-5U2=T>PJI6?!vGoSBRK^wW&L0d0%`l8G z+y8jwH+i^rqxRsp8+F==vvUgWqrg1{N6x&fUp~=l)dvce<%5HV3D&;rFX}lbngAB$$ zynChdLH}JYMXua`z4#lCV>S_39U5*rOJFb=z{a=>yXXOoF%0O#;dlUpXrju08ScAw z>r~^AXE_=u&vUX7m51+yM&KODt-MkY|nHjrD==d!tfrZ|>6IhvO`*haYS= zDui`Y$@Kt0)BG?k?bz6(-pL`*cfzONw03cjOWCk)1CbHUYbuM<(?Hinn z6D|GElp=0FzqWGk$|SnND0Sh4avvg4F%cFA?#}Oz22OW4UM<#%leSBdk{i|)SzJiEDBl|Gk=2;Fr%YduZKLvr~ zs7h4Y#c>orOL0Dy(xk}I4VHP|vv$7E^ZxCPo!82{b>ukJa}MD5|NQ3VW%|ndhp)A- zaV|W@sg&ptX#UNjDNuh18IY0?4u%A5#T2YP7&Ux^&gKnbpMdI-HuwOXIao1XK+6kY z6#0;)sR1BO7we+xK`lvOzXsPjSJ&>v@7%2k#WQ^5y6ZMA$~XJe>n#oAmfJ4q$4iH( z`4Jt94pzKX*S1GVmVKsX`aO3ZKoo@m$b5omw>0AoUcCmjMtfh)Z2YKRfJS*y%dz`k zaBRy1g5z$EE$3>ZT#$^w}0Fq+>Wi`IXvdIiZXW z?$~7G_r^nUdu!+QC$t*OoxhC8%Hf~ixKMp}?^Y&xj1vlr5~1=0RANv+)UhG!aXNwB zaUULyaHP1d2z0|C^rI1slL#U_7OtxTh!1da>X2iMfEz%?3xL|rtcpK(LKPrNrd+V> z>H^rFhb@JVD+$UlNtWoNA$2(n&4`j>X1ktZtgjK7R7idG+681Y$7aMA&I@>E3_zBp zcEy1qu6&MbiT!SRvD$g%&}6bdbg4!qkTw2(_|;B(Yk86^BD+`lwl5N5c>8c6#0`%< zxH%Yh5PS2aZR7pZtW_WVR;A`N(riUqOj@&U`DtSB_+6w|Jp}YPeg&LL1)ORCcEtm` zg4}uzP#h6%aQUjtmvP9`41`gBbpNLw#gQ*46u*f3IU#~_PN-||eINMl9~~UFdcEPf z51mxi{l7Aiyx#wdTW1&VPi}L`Qyf&P=91x%Ji|XQt2OB9I&n0D-O&KH#{)Q+44^w1 z!XO+QW}paC6$)Tgpuh)UKEQ{O=W)i& zGzu~Ku%Fsd9xG1;Yd*$I7Tg9@ZIC&GMQg>i?4hngyjVuynK1zB#PclFBcCK#HiiAL zes+27(v6S0DM@MeQoD8dilpZVWfjcI7cDGhfDBJl*r|E%eRpSXU1oVr6}+A>P@>tW zZ~u0!X0=nXN|+@JRDPlS`Z#8AUNEmSYJ=?r;P_Rrg9^AcAMC0JEO3Ehw%m z^%%Dwl|RoD$OTT?nGbsBe;Sv+cr8uon30Wto~OMZ?(AO7)3os<9K(sCawVup&>Vf| z=DDSv^bW4yN_S0l0Fgqv0m*m-_#a{fq7iIQ2Cy?8s60R~94jL*i6aQp7}5-@0#YRl zF%-Zdq!q`5CXOR_Jk{(}E^d|^j#N?*s7zj3SgQE`;K=DPyDpJ-UG_(#bHPCrF_1A% zD^%o1=AF0|Nr=p)u#&b%8%yooCtdsJRh}6GfLDgc!(3?(*3TJ%ltVqXu2d?%fB2y{ z0{{#4YWJ&th0%;xaZu;cab0-fd78nbUBC4Q+q-A-ByE<_vSlf_GaIeO-fuMO=|Uz~ zF_(Z`ePv*edT;r2CM;mg1;?#|Td9Iut$yPC=%K=I(jXTb@+n=X7@HBxu zjaA%V#QQ3%Z>9zdIVj&7IvVR7(59_AVU1B)S9;r)hoo$2XQc z2k9Lo`qcHb8CtL~Lb$T4{mFGwaC6*;Jxm5p`YH_QM-vqaXl#H^WTF>z$VVFwV6caJ&lGl`1&3 zDmcvw*fos#F#)I{zKW|4|HB$TDt?kG<;ToAy8h~KC+%42Ir4j`utDX;XR zKez5~yxbT=zvz^jGu8x+3zcb}JM3HlRY0o0JFYLZ4)n_}P8jga7y#9_oyWdnoh@A_ zYB6hP(z?93eBpM{{(0iZ&HeYKGpid*Ve$qzmKlgsb{~lp8OLPLskd^YuWz6Mqbb4 zehQpYWB^;^k!lhegcBGhq4omO6tWymm+mgasY8M7K+SQW z>LLR2F&v&+bCtmkeMai%mr-d?1P0-2nG|lLFLuzl>K9Ap6BHtW2?DWiE%1)#V-WKq z@dP|G1^};U9EHATvz+rRovtPr8c@qmt@D+@_Yei9#%j%dSh=`BI_=hjmowSX?xLxc zl@vzT6lHwRVvl~Zy}M5MW0+#i@C{;F>DN|P?_N&$60>Xtm?>QQ`M1xWxGQ`0nsv=Tg`8D~7ISoqPbC*_=^-9NcoWY5tA?DqC zHy@s>NoR~kir}!6Vyi?sIK-w*O!BzqpYQAkj@v6W6F&DspQU}VY+7LyqPoo=SVT-z z8x0P`AwWmGx=`u7cIen_py{>^rKJLUO5#<6-EUQEnH?t!s+nY3L!QD}QQPRD!}{Op z9xijN`xYI1M?|S6A^GRmR&RWc%N3Baj)}yRu>D155r1Na8eM(|mA_E~r&ZHNz{j$m z56pEHt{)jSloE5H%Y0D^Sr|i_#E@mNLi&|aeC1oQ$o6CZAL>NnIs3n#PWFDVv3ZRO z>HIuzTtg`A?0@j++|sSlBSv79Bit4GXSpmD(`7l>wi2H%@Ol&=m%ZqsT(lr&U{9oyQ~=o;P`x3;>Dap-U-bzK0|d&=+Pv z3|?$OpfVz>tBuZuE+sNIr9sWNM=5`^R-d%P_#(l?K}o08%8w?1bCOds_=)7bNfg(g zloiJt9;4+@^kbwTfE#-HH#k@0vQlf!5}@ z@~6P#1oAM3JTVVU`b}vuO2E5PvQPaK90o*g|52U{Z|!%lP$68CzcxGDsbATz;( zgCE?v*xH!v3c*vA{L@^a*iNh~116;|4M+$Te>WV#VK{eb9 zXaLX)Kn{drNYPxfRigxnC7FyV@5nO^$FuB_x}GN>`tVxkJHx<6v;uko#p_c4AG6EKEx6hfTDkmgwGM2|-p9t7qmnPK0`Nc1WJW~b$ zeMgIj6VAcwRN@Vi$Dv_(REFDesN5UV%dO?lJ#s8I7E)3-@DIP@2gHlBWocvhJHz@< zU1%=I#IbtcJM6Xv=dP;u9~X7!29==u4c7(S9&t{Z&U2)HpV>OuSFeNDs)MJLzh;0w z6=0rLer!W6NIaDCW0`M~KsJd~vJ74S0>wAoo6=JS@KI4rE`~OJ_=n?hw$<(7AQFRX z`^6=$if+7aueav@Xs7RH&__lh^T5*_+;QGT5;b)# zPXJR{z+e!_^LE|Wg9}~&d8Q126r2$llbCw-y@OFF@L;6NWK#?mB4x>5+~ zX;r}!hs`UlUsVe4wQ5TFT~vPjJ*cX3A7=a&yjQvXSmVb`e*)P!fi#Sid#vJqQ$s(o zzOR&D!SDF6;y(K4(S&Su2j}5alp=h3bmzgVi`NGaX_hBkp#*1h2#W*IT!W~DF^r=z z>_ubf#uG&hiV49)jSfTerkjN@7vNjiE8v(0Aw%+M<`_;>yNWiA%8;jCQsb(tx0wg7E9gLO`1ZqFTdE1>n5b|PS1iUR zUs_+je=dwK5W*CAM*aPS)gK>N<9B_`@Ylgzz?HuaUR}HXu5$e=iqu18vQ0-HX8W@w zQ3=0n96}ahxi2!TKJ$Q{X*CVg7Ye(O^IqUS`ltP&yM5R{3(t^j{P@PJi-+P-COIKo z_X_}yA1lDZMKwkWNRmi(2<}G{l?%YJVNr&mfUXFjsInllpn^CG+tFUYw8&t#IU`b} zmZZ@0_<6fk$rb4TsJ&L&EbY-wx9LfHJJr|)S^T*$y{fkXJ+%ej!}%)k0?4yw04yO= z&tjPItvJzeuR>&w-5X|Vjdqcd#50q$_6DmiSd-`?xMnt7DFdX&*JXla7Tfu8x2woJ zANReIQgNx>+@is|J3fuDk$T{jvOK`@V+wT6z+% zR{jifB*#FdN4GzUAxGus*;MJ(qFF-y>5MXjDpcTMJW(vcNE(6_0hJIGSmL3oEa)juz*S+^ z9AEo11kmTpqLv);q`vst`TLa6lRBoN06HA+WQ0%BP*D29j>yB7}~excE7Y`q9= z!q1QaP*>GTldrG)NX6bB6hS(PAswRun3%Z#IKJN`)b-zZPNE>#`Qtd^hokYDYDfM# z!c{*;@kB#}jG#`RpMU2K22p z9d2o^Bqc&52_%)^?%ShD8-(;uWRt}k&9;W$Xx6v=^T7ZQpCx zl*^B+zu#zpuU7v6?7-Iyz8G5|a}Ofk;zIc&NJr@Uhmhx5`KLQ##4z+L*B=>tVoLgd zFdBv%-N8EU+s{k+r%1++vEC^{5dy4#XJg&IzMBwAu*njqjLd5IbJ%5U9S&88U^j|j znBx4vNR1B5kl|Qy24EO~-GUa_CJZpp&e=YfuD!(=*%Q&ZwJT@uoP2ylrb2kh{=lwK zyO*h^MyXed(iU-lnZ-otgW?Bxu9yF3%m64MBIPoRa-_hX$okUO?&g&FFm4!A0?KT9>rZNJ!`dbFyk6sJL`>6QYB!px5 zY=SHQL?!)BLiiQ91QF99}GC`=7etqG|RceEG7w@n2A_`8u?*9(AbNgcSplZIM~FRay6c^0YvX5;kbc(X88QGx zqKH!=G8=3L2gCeKd*RHTw0|dOREi92vdAa=C2`hRA1WCz%iinI084`@M9BpD z=n2FzOcXFERrFyP3Sjt!B|#i9hNH(7LFzO$JN@SAPs_Go$4xHOYnA-HdG6#HG5}hn6u#hLMY((E3i``l@9^H`g@$vFWI4Pp zcZd%el_p7BhRUi{ObC)P7{AA55{<%Qj8_Tsq0@=J?{0s^atIRwG5*Kly~WLdt|%&h ztqxuTK|T!#(B*G5)lYoQ!!}>nRfHabOI7MaH$MwQNb!LyKf3-(`H{_c+D<<^?qWWt z5I8Y>_?I@h_oGLfSBVr>IZAz+ry36%9hG1CYH;iS_kTYZ{jGnc{ff=V%J2MoZQC$` zf3yuExQc+>-`u;}*-ZC@8qlFxgr~LlQrDo$2c$8CNd)~Qgh7H80W<)JmC(5Wniz+b#{=Dt3@(jh1s0R#?ZO*)9AO=yVNlE-y{QOBmS#ZMeZ z_B_e6WB@e6EXgbBWZ(8(HbATT)HA5GzFo7OgAjD zyK&gV>Aj*HGfx%Cwuj$bT!^Ysd=8kU0?FcDnlbul_YmVxw^CEtek}E&;`_~(@&Y_u z{i}7bd|z?;p~s8Z_dJdv!y11SLNb}?)jyo54!;8MGAr{*xkEA zq(C~|pBKEB2m!_G3eAK|hmA{FrKY9U8@KvLarzZE{C$ZFraeop= z8ssy<)nDfT%3z;kbls}CM=@B4Et~FsyW3mlS&pmE6B&v=*2y(ntN3*LZ~wc?`~R!| zVQ1a4VW~gh!nUa@g}0k5_|xxO{DEjV&;Z!Ty@=HSq)R{cZHnEefPA~723xFGs~hUO=EFaA^mLr&B+hjm z;D{GCMJyn@dDU}V%kDqVy+O~S0RYKkj#+ZGE>p_7uvmSiZtb;`gC?i;oNro=rO_zd zVJpsdcII4t=_Ta`7Op};_F1h28((rfV&x)$Qps-~p%{MmS-p}s}EzkHnLyA)@RuR z$v)J@r3HHbAO797^l$x*`h_fqN*qhcFwtQw4rptTkE$}2FmaS5Zj8s*lO}xnw{qqdkklU zAxDh>qWCnKM;-inU9bGD7F1eo2wF{53Pc`%g>%QYR%~p+WFOANGgF9~ zpk7l`tA9>;m``1vDFaa47?UtsfMmcd3Oq;94<9&fkv21m9K5r*uIx@*lEZbj3`61) zi*k_&x{}ww5VYGXcd&mB2ZXHGg8f%4$KgT{3)R2;-KFQ4a4Nt)J|_O^xbnB4(kx1T zxcUdGw;y}@Q!Mv|81IiE8IK?xk5%F?4aXYoSDLO{d>7UM5VFRFkX-QaJ&MJx|t{^vKF=7N?^N($YeZU;nVAdCoCignHd0cjr7)xp>iyXSEtlO&t(ko zOc?-!Dib-EzDLG=i;{sSGI&*NYdFcQ06Z08V*d&A;SOCaN>0>fQj9Y=L)u?-Jl7v# zsQ>k5bCM6otITy($4Zg?pNs#o8_f;CsT;tLTK!wv^=~yGXf+h<7nyzVCRQ1L-Xr&?hTIalM3KAIO`>gREqRO9zHf`;YprXojr7sJk?xe%xO zsoek_3aCd45l6xhod?h{J`NkvI;pYJ_n^kHD79nd_wXZ zZSx$9LeX@Ba}IJw>dYc#G8K~;^T+4-0Ge_FInJHKKNQF^qe>Vf zGQr^{S%ML9fSUVMAfF7Cq8nT1N$ZXMHy2vh+hKf3*fwHR=ErxHzT2{0aFEEOQ30=B z1y8O1sQg&_$I@S2CH_be_p6n^Q2YrbV^j7EG4Y3V-dYN!*ZZb!Xi2k-#8v;{w+|01 zJjg#5PJV0oTO`XRb68jc_u4j>+l zASoWh2_%U1=h^I-%Jce^r;9_XN&KF}!iW9gB_eU4@KaZQs8#60zy0r=@m{*HRuhK_;sMG*)x0oDM9fpCa&~c44T7SKH+RY7B&|DPI-XI-8t1>* zDh?#hbFeCI>;``Fyfy|sa|WQeP|bR6=c4GUL667h`I@>vVBOz>2c)eNKzakkmZs1i zeUowcFG zZ3h=_H9Y4VU#j$Mo5Tp~|2XBaf~<3Y|L|&OKfTW}zEC^~rV>zaAIv;?jjB`}0SE}v z$Uy8ZPzsLjziI$3eMkBG6kiC1m`Xn-+&)RQ@gGZKd9wJ{XOrt*y0Yt$$4+0`V$qlhUa)gNDEmThJ70=a;j>SU?=w~A# z_jm0J@V0dpe4>p2&hEoWy=wKx+J6npe+c|(D#&lG(*d_yRdIhf96*S!|7ciR`E{pX ziaq{$T2lDgz)lq0zdX;#?TWwqXm9VTzR#bk@?*vy{?*@V-v8JBTC)=;(lJ@DS&kds zXEli^1ep&&=0gNt>DwKR!Tj~0Xh1HXcg|nE;NfHqt$`m$48(5q{>DO$Sm87QXc&am zMDXG{zK(rFibw^$033~;shC{Db#d%?0pT(Zyz%965UA=T#5og;F9%Caj#+%eQ#Q9V zVlY9kMh7Ei4EeMwh=vphB!bsi)yEGb*U@tq13XIxKq&*UtmwRDu^{ISZmHf9T_yS7n$78+#je(hD^BsBteoWUPpi-^=7B=)$E6UABh%b5onDMV- z9-zITruRgHfsXqJLx}M))~mm2^&N+b^kYWwQHuGs=13g-HEC9_e{XAN&=WVY<8Mk9rUpF}D8cfSQvo?Qy#Y8Y!H|b)+>g%h zOj#RCISPEr2$hlKUS)ZqXE8S4B5b}VA0><_B-S*JDDA8PDN*7&o|996SyJaYlAqfY znU5-kE%$6|biuYlF-guc$3c{A$E{z{0!<#F9<;_Ww#1%xl%~y0!$Q@ug zQDRa_z@M@U;7bdBsS*RL;tW4D09yGK$%m={w2&YICWi+w>GzcKM?>xUYv7Nr|0|LY zQ}ds5`Axl_rx}5Y-}{qZubIYi^QoRYlkq=)#=h}C`|qu+*%oPKxsaZkj;4^kAb20Z zX@I}AsU>7*-bs{64Pe3K%qXa?k&i6DC*Elau)*QL?eIxX8c{fD<>T9~-rG5#N_2gd*LXaX_D{)qUK z^US`5V9lSVDUIs2jqmO6ugEO-p4RzNPzS`tPze9)|K-K_(phf-S8F<5ycqDT57o|Z z!|H4QAvl#cW+(B42fEaa#c#o@-}rw*rSTf%*-)W^3XkR+U#ulhBWy|Y%su$=?K4d$ z=%zXL2pHyYwHt*mXvl=r?g|hhRYm|qF~u8bW(AaSz9n3t@hJ#o6}t_{_kDucggT5; ztT3K@+vrvlj3*MET{-eK&w)Hk20$!Vt39{hzd{`Z#p$U2=-Jj49|G6Y1*f%m{vqb| z_;`c*Fzf>$l~tK!%SajYKE((aBY+x22>hd4G5p6r9YRp6LcLiBiqm|UrOiF}bVfEFJ~w;W@^>!6Bq8 zLAxxX7yv?vxYnp1$|U!&Pp=^7L@6#X zKCt#bAORe(nw2=~%f9CfMw7{!n?4-dM5L%YW@!@SynqkG12bM<9x##^}aI)3=;)E^<9!RHg7|3AnP)jk}KqfQw8w zE8qZ<*=T@yQd6BQK-jR11$UZDoi05=fr%uxMTnYbGVLxhjY`Fkk~~SFUaw2XHJ9!= zs{=eU2B6bu>@^1CtANZp~gGU66dR+)r_J06jf%a3YzfSzAu~tV`{bC>v z;ex~MyIDyN-`xc(x|fWzxl5%x~@xG821-oA9PzWt6yoo1@(n*82A3e ze(lT?SU&q(;8oUv=MxQ%WrTj^t;&j;x$+5BfrGT{!z8K1@-R1Xd$IOUi9?QX_MsP` zJOOE@2+f(?`KEqtOLSP15*#{tJfh69%$B)`b;LCDf{JI#%djkn@>nH!i?4Z}9C&1q-%S<~9dlt{*yQ79EQoCK;~>{6WNcj_{KCFF6V2 zReXEVH&7UXnh*c@kKc#C^*{Q%aQ(eo;P4EFhdmhg`w*94zex0fZNNuIdYP5qze$s0=s(AYc}fao`D8`EOk&ukP_(jdaLq5&-1AjW0O!}MURbcEUoT{4M0X= zHCp`yQqQ^17yv{}1a|WslQ@YSn)5_Y2q@f#g0jeLhBE9(q>A70Ih0S^0BDw;YGtQA z3a?X+Qp$*k-fnreJU7<5Bbn6x`s920?uU_kVgyRA(ge9-N)BlVBRG zUS;x?%LW_&38v;??w<=`+q+@cO86Ly0h+trtm;RhebZ8%GOo%KP{&zDf0v)DeWogB zP+w@Vxl?3BaA{GFoB-xaif<^D6*1JH79|RO_#DcodQ=5N@1@Sdj+Jp!)0w|fOD;9w zq@R6KhZL}uF9||bGjJD#z0&Zj-i^jEy!xitv}+Kn0V1;{rDWab_n`DsKXm4qfL#yZ z_Vx(=2mg=nz#n}7Hn3z2x$68wg3k!j96>+x2!+A}Q2U+dWWTd;bN{f13_tVpCqDN1 z(jdsCj{Wd=ezS2wN^Gn<{yx2eB6$YZ@;|QV4J#f#^Lw64a2;BC>s5alt7xC_{!!dP43$~QDh?6Fh8Pu!I$R94-Bf&dE~=%xhzcYm@4zx(b1h{*`}WCC#< z>yp$7sYW#oLkJl%*!$7e_Uer(oZV^eJVXvt0WtW?-LUaBhgoh;2wdh!AwGbN zSZZedR6aD0)wC32C#%gMqYVC&yAuAh2TU>iQXZ)+*j(bz08|v!h!Es{;BD{s`j{|# z8ln_k2nc`koAutym%Xz&m+a(pq6y9=hiG(HRRXj@`0!@P5Q9RN_F*!*YkCCq;FL5M z(5h4W@;MJX84E`Bk9kzuEsO3Q*eo+gQ!clkln*GE{8R!KLDt4yFNS>d7_h5MNE?9O|$J~fJ$fOK>)_(n(hjp=$32i=JGSEQz_V*7i z)kD~loU44CCJQm|Fvs(_<0RPdQ0~4oqkpG>a6%yAxm#=0M<;wgIwAVbC0#?EV|S(+=xZ*2*#vWhURFf~!BXst}ECL3i^XneBX7J=F zoBfxs`d6cfBP|~NF!4LH8?bx(--p5O@0Dt4GaiH^pHPOQxFsIl_`ksRo&Ojd56M>M z`d~<$NLX32TW8m83ODta_-gA5|m4;$zJ` zM8k>aY|POOB9}S?Y9LC_pL`kyKrI@M?{_Z-!61*47PYMt`{E1wETA;)F`A6W@w%0F zM<5e!aQ)MZqF7udz}q%QR8$%!g*q8H9MyWjXybvcT0Xy}J!55kq)0%YRe zzW#5+c>kZ4lAP#bBLa5z(BFrU-wZZy{omm3JO3UqyLv*KEW(ea3_RL-v1f#N5L+t-V;1DrcgW27|Rz?Fb@|0{oE0k2aN%ikY9>84^Qpfmu%L)iJ?zYF~b{|;o~ zb(7>YQ+CV)UH(b$M{xh`|0&#m_kXF}eti9e%__!R)XxiN-2tQIlW+vgjuj+1xU;e3 z*|r+WF}*R5O(~rz_~Vb<*tkNcDS=R!a{rO<&&Edy3wSHucbK;*4^=q@ub#}jIr5O(^=|V z61Z-y!5Pt2^8)Zj{c?UEc3A^L#R{ZhMcU<`w|@m(W(EkIZF}~#*hBO(@aE)zL(Kln zxdy9bfz`x2gK)g9$uOUSoLh4qt*@|`Fdz|#F8s(bw*bNdAS8m}*1r$=@SlL!_zE!Z zH4vnx&T$rZVKR6Z2KzsP$!HsxRR@6-7$@TAG+Pc14_WKjjY@^u!;v1={z(i0R_wyP zVY?|WJlYe{S%=sL?TZ$tDEH6f1Ii!Pf#NhTtb(&3DvMK5_$Yk~-XGEAU4C5GKTf1sK6s@`vyz)+Gd(>zz;z`U!5| zF**A8vdPifi1d^=2zyI*TE6!c(pkIxPH*SEYh1u5MgZPQ?Y73rP2+fb+GZ=&vLoP1Xm})`GI5Y z@SJ0yhC)W8QKw;#?;xq<@#~?6ZNj_O8uVxt97rLO7Q9aP;SJJ<3~T#y<)x3lY|Zim z6ca>-tp;y5;e-$@_3SsUE?oT0`_*du5hjsmpZe%umZmbec{f@AJOAO{wg1!K+ui%K zAC7()#$rq}JKR243M^Nmx1+d%%7<{`UOahs)h28g_IkU=TujgPQs&s+ zbP(f_)DXVz+BPKN*v_~bA@JPEFaDqi5m%O%@AJWA5s`dTpw$dRKdtL3<3I=*;nH+O zI^-~?c~&9?}N;A}`sWk*^ zbpC$2@RD5pO>bfC_4gge8Jn$YJMocE8Bif)aBm|$_xJx{?+gFl-`*Mir@!0%%lB`^ zyOWTQK*Gp#i41&7Y+JifIP?mWb%jxw9Ux}ei}ZKUad>W=Fr0l1zZGRoQd;!QwmAJ*oM_!B#_w z;5n2}BLt95Mng>WFkO3^0lAORX{&~J&pq7QKR90s^-!6`xpcI2KtkArDp+dF=KS?V zwBQHU3S8g^ummGWi4Dtvgx{{l@Sp8gRBnLKG8>%9I?oN9D69Bfgm?lxtX#HKpaC*T zVKH&uYIZK2zcbnWQ4kMzS{au${l(%#yOX8+URk^0h_8L;2NQfGtBcOY@X!25VF+%)<-`+E_d!#PMSZpIK5eMZu+q^XT;H$6LO!Vz;zlH5emU8TeS+ zC>@xtnuJ+ukJF(*ryD)E^9urYq=Y}(Lc$3A2qpyQihWPo_31OM zzUI~|Ce}=+QJegg%A*N;XVQ4Ny6{&v>(#pl@!>oDNjN#HKmPG&0+>!bcY8m5ZF@h3 zKmP7;)TpxV`Ew4ue$9u^zvgu>th?d*n(ZuiNTXI|jlje1R>hVFSxz9w@HSJ%0lfuK z4}lN8x|bit0$e=s`!Nw%tFVUWkaQBsPegW7EE-8zTp!@@(It><$b0LLqX zk2_mOEx3}DXBi{XR=qyco2dAN?n|JN`sy96K6u_2ZoWgo!UE$91@_swI zaeI7lqm|}j;Rtr^LrW31;;tI|SsRRb?Va}{c<23y|NVaihxICRmO9M3aK_DEdx>qn zblDx9TeGq=E7WQ=tibhIqvAugR%KR_Fo;tINg^T51YjO8MGt}jP$pH9aEi85q=R@l z1T-6TsZwG4ljwMu_7O#491@<_gZ)WdxzHX=c+#*WQ)p~WB&Lr1*y?`FFEc1Y9YX5A zi23kFzm>5RNt9+;p>imQGaQj`PL3hrnxjr{^|?v{{^AXQkYsl_T3mOXTykM4AJupP zGr$doqynr;FuC9DolbbK+cAY+3}7Fj5;eJp$7Y>R_6dypUm8KDrt9dQ0t%LtTnHAC=8C9% zb?Ez-#_jOkclY)lz9QtuzYbG6JHwc*u}XvqA(w_@4*0lzKZQU1j%wcxT!)BCKuF+I zd$G;<@}jlBv`8nliXGRgcIvxScn%@VBE;5&iInFErju665f%%Oq(Vdq&*MbO6o01T z!pGhJW5~I{CFpZ@H4MqJ`bcq)s+L6_|_~7v%e+DQ~?88T-QLDxW2o^u7Vv7rq z5MXo2sj}kFr+g|NKz9?&K1d3Sy1Ni0cpc45X0w*06`>7QVA=52LI}L27m9SPSfi( z;N+^plZ-%-g4D?5`S`XJX9Qg_6%Ocm z9#50-1Z1315ob2IwnBK$*7{nMudC+S; zwHU;MiJOl@bqPOyLx8%TvwS2a34SX+{j{eIeJYj>^HEUo4{!{$l2ULvs{jay2^kAh zxG&C9gAw9l)=91I4C-_41G64Bo89@K=_QUaA}*o}6j^ za8ibp6toh@aD@u7gwV%O*p+>`{#v&5TlJMIf90os&^)ji!HR);vq2Mnth>$_11w{! zQnmpQ6<)&BI3SEcp{BJG`^7^@@Hv-Wj*nb=SuWk;k>}Fo96TYwlluKnCQO# zGMDy(IbwL>3;@q_@4;}eX8B%%)L8|3Wtt4+oDk3MZH~t)QV6Hu_Gz`6n=K@RNi&;J zD$zYeda#xl!8w7g0*2i4i0Fy30PjQr+!#>s1tQ-vh@uR59Km0)w_wFeAX8LhC1(FJ zFM%lweRwypyz&>@!0JI^21@T!Jvvg3bNgJ6z4Q`W`)g5U^_#tRd*zPJWT;`qYFp2a zs{XvhoZzfGMBX*76OP5Jvuje&z3J;+T(7z9On*mOv#mqbN-~!}$SDtSs>*zUK-UA& z1F(P$=_rkJDO?p3JyD8)1Iw$D`mTN0XtZwmt!rJo{e^KJyy6Y=#+#EkUtt8IO2tw9 z1fO=7;iu6IglCrxvfQGz$|g@$7$ES3qai?V6_@qQwRXo`fzhYn84o1&nZ;3wX6P28 zI{&WnU8s;uXWi%Gd3~#(8<7BoKkE5#I7-37x$|)f<0u0!8N#peJ5Ym6g%#Qp(19@$ zQ0t=Q(LC`-JZfVAW{(0vSn>%l5kZ9<#_VFBT|S$&f5UH{|Fylvg{Aiyl{kImQ+FAE z0iv-Ux%6Z%EHi9PPdb_^uMVl_X#PqUrU-wAhVigh-0PYx93T&~)MMHL%s#2h zE64~3EaR~iW@Gi2*^uYQh}uWds8;W+tX_PNHs2DN|Arr1>+N31UQW`iX;CgLOK~3p z)Z&i4yo@`&G!)?0G|s zfnRf?+Wo8wL0M+E)%Y<4&7@y+4>;e(fW z&izlYGtaaXaq*}(TJd5?^FUuryaALUp7x5AD=v@OmN~^3QU=r_xa4A5Q$c(kXUKh4 zQFySj{lNPXOA!_YIZW-OKX#99^62+4$|mj3`uogZYV<;9C6=zpfHo1Tjx2Y!g$(hk zhK)BACS^pHPO@j=+I=cj06f|_jM@(^OTHC&!z|-24zV(PkkGcmtKYb1I#KIxKvy7OkgFY@;{>%s_Kf^q&RlDJJXf-O5NN-Bc zf^V49`;&&i%nRlbEUH$G;?tzjY#t0PI5Q9MEb~mM{DGBaxue7RS*_4wG?GAQy|MJD z(K>f<5RtWR=Da~d#ssCR7=?;YwYpr5(3vv8?Kk;{SjD>tuaJKcZ!NY1!8zP--aJL z7l7atnIokljU|yFD1&b&i_j)LaFE~=p>I+`0t{Zy?!yFJc-um-UN(i6qJb&!|FhxM zA5DsDJ(rJu0)1_ zX{$8u<})a=KbcTb6_PQvD(mV{k;f~-vZI#x$ecknOhkZd1#@eH->QrUD!Q8?QKwtb zFlk9sLE}7cqd~P99W&cW3G+#3>B{|_1lImoUhVbZ3b8B&jw)h{qINn5O)!M9c;kAO z%#-nW(fk2kJOg0N-oaql-mg}pRmaV9o?8rg0Xep3Sp6td*?(g6pH8AEIIwNrv21}} zf-VG>YQ)G01OS)$4&3C+Fe0wv2b{Y()T)ckLLByq58n~1@GIGMLo=@bhLwabXSX0C z4&1bs6~SmOv1z7&`5wRJKJF!)QIHfVK65Do^2O*1@6R4@W?aJ3R?PdceL0Er zfCF>e@W8dDrNQ7w1+gL^Wb93{t;}`ngw{unw-i;ItAlZ3uRI#0pU00;+XCj4zsj&J z9itd887e8I&GOMCO$x^77taIGqZWwZX<9wBt*yl*p}@t0V90bW&}GVUm4-2=lMqPb zq`ehn@rvj4vN%})$I%SXg{t5nM@hhg1YYA?@IC7a5NWyvkFeHZq7dEtn{*kLX&+wV z8;Vp^x#;S>9KMlWSG~_c?_uDe%Yj2xfp4t+V!<=1XV4V%xTD+s4iF{)GFn zKXi9()~;Qv)@l;5?>3KzYU|{|hIiMvAVmwO07BRO@PRB$sRVDS_$x$mQ)RZubd=l7 zG8(KGsN6}-Xx_R}0uH3@0u#WLdw?B=TP6}BcL&=_7M!iytAIB=|=#ni6VuOlSCM8=wKSS_RS2 zH`;#QEshgb>_*BAua1e(^LrvDkHY0klZ0e5ypFv0g5E(`)`MFX1>vxlChnJhuUr!y z{g!Dsa0sKr7ymf!N&%>eYyUYQzhFd8{NwCF+y?E4us*noO@yXeEl2%w`2)u$a`5A# zfp#isn}sMaj^O;Vs^P9da0!Kel0Tij7Q1mGC(8X7$E-{l+BmW;n=I-KBLlStDrgnu zmJo4)arP`RSLa}bpjyYUlDOXQYFJawq|&Q&Iq9zSvOxwk16icq;rLz>fZ@mS^A_Ka z7gnap6l@H)EOyAqQ`Mk=Z+B)LcgE=K*#;) z+kc|DlKB(_g;LTBIMv~|ATKk8RO{MqdrV?TloYRqg~#f10Ov7b-lc8{XkiU82mA9> zMM+mPC@@k9jXqdqC#Jlo{R?A=CS?NQBxFRIrI{6e6fUj;GfyJc87d-9yX;~!U(zpU z6T93U9?p-tbpFw=7o$r?j3aLhUG)u#k?RwEj7qPeOPtQAZn!b4q5NS--;K);yt@avTpkmU(fV3`%104^Wm@$mT*KNq zTS53`4hzA+uILSA588#_1-5mw>F>2DuLt2AL_)Y7(QMy{%TvmebEN}sKRWE2?SwRL z|AWGW(3pKA0XP3y#Xe3_5wI&XsVU)>4|-lvoxC~DrF}b%`XS8Zw(s;rkbyOmiJ}Fz>L) z<5cl}l5nN6Sk9R3y}S?Itd30sQdvkW5jMDBPF`}O7ai$$z0{wM1#NtM>oBrmUX&4h zIp6G=(`cO;w4ZN!UH9M`>jjv}UpO><+?-A*d}=!O_8d2MHLrpsKWHIv_hA!F)`hqn zzLZseSot8@>?7%JmGwb;R!fULAB*=ns%FZ zqBECr$I=8~l7*Q4M@Y2jUt=o2?gumfArcOPvcUWW+Xteb5$9bOlIFtG^sJ%W$u zrj#l@=%O?_t%`|{hS!R^Q#Sf8TRv}R5!hk8$jRbXW!6F!#$Ep&^s*0NR~m6&8yxw| z!k@U{zj*)aPCnyr*r4#I~kRah*zORGY}Lp37584l zYdEz~&f1?_>;sk3SWF#x=ubQ64HP4L=wBuF7yv6f2P_@STTDx1>>w%)>_J0-1e7nm zp9VzFslA%P3Si<@lH&vt95q?McpzVRS3@$P-(W;wSu$xyQ=F7U>3vs7f<_$uU&35b zdrSETOEy>&PGfkCrc|mNA|i8Rdrn`6A`Ws#i9bP3Nxbw&fuc5o4%&LH?T?{LzIBY2BQ!|;<5GtLuHt#GUo7*dna>{-hgY(Uped_j&S>eOc2}KWw5$N$ zZ(L0br7X1zaJ^X~6vgw}h2Z-6J@KR%VZuv)XkHjjUF?hq$&y}Vdly9LWGCf~ z8ZlckF@}m5^H9bbv@w3>;z-Scn}Rj}5QP^44%6zumkv0@MUdrk>Pd=N`v1}#rivv! zqB7h`x#QWNXw3wfsx@Usi8RJo)5TbeS)~)wV}>Q(i<>uB!6k1&8|ck#oXQ}|;n-0y z6hb899wM60%92dWzfG#T!zZzoLf8Kkm+`K6#OUdp#f=F`%2{;M|>BO^rOJFMy3~H!v*}x zHKx~M1=4e=qe!38i|Q9dN~cVibXyVn)hz6L%LlV3jTaVeY$+u1Ie}po+7I|36DYl9 z>sy;G8|O}EpO^*ul5}vJN>F29(r3R}d!c4mMy%$zwSFD0*+q)?I_ay441+r-HCewb zwif?K$2E8a9wSy$^3#kmde{{1cZ@2Qq___>Xd{*2PBtW#)di>tXR|`tTBch&^uqOk zQy|0zpqokb473X!+H$g9NFQUNv(f+4w`d&PDB{ygrLmF>MRU+A^_z}BjXT=DmWja} zI$w)GD)96i%p-W{m{-{45B;=%aMSRIt|f^?8erYk-x#DBp@`zU8fZvyhb9!S{~JhK zER$`0Ied@GBYZo~n7M5xJ2&pbFhX6AX?a7Nn`98cbDr5z-|i~hQQZ9wExCmk4SG^t zV{IbpBCBEX5tQcuw&4Pq{EXQiX9GGUmfed$-xgwwbdA|7)h%oR_#Ah`;HW|7q7_XO z#&>NRbi3Snd+oan^nBBSw3ys{V^d8SCp}x4@Azw!vmAZPVwE3NF|0I7?KQW|MO0E!fZ3iN*M!ZI+*cdsEN($|PIBPio5m*3V;R-u#C(+E&!y?WR(+-VdqQSd zh!vj*)1<0zR&}pbgMO$_9ABH|aCzXbk>NnXxKkz_YR^AUHE;Zl8cTpzzj?bak;x7b zNv5g!RgI16D8tQTnOe9D))odGJDYGLy6!EKdgbg&6Z7QuYk zT3PAZQ3Bb0On*UcXA%6D=_l*aa-3x822NB`2hnj76}U@q{DW(a14X!yC5FO1YnfJ4 zbIB-W%4`nlwXr+W;O(ec{r9`s=1xZJ;k zMDoB~^+9BwN+Eef&Kd^yN!^kTs!vx^M=FAYr3O8D{sq(#M##QkCW+_nGEza1PH?#2 zrXr{sc!>HeA4)hVVOB`5oWe^2y!C#4el@V5!_e2v5nF~*MRxP`pDyoFz3gQ0hGBza z=Uroh`kX%Qmkp+qf`}xwf+Db=NQ64ZB5=z~z6LNFDjosFAFx=p?RF1!>i?{u1RLNO zY60@$B*byWf4}jhT(KrL+j0Kqu-Xapi{?E=Zt;($!0N5dEe)5`h7KN?`Ym$_QAS>N z3hlmIBNY*r}I4XqERCU`$aqsnk@1DB?lE!xd(oJ#DtC!DxKCvJHD0_u#>g(pp+|hggA#0}kz!DbvFBTN)=| z3!3IdgFm)l<`{DL;8H!e_>c(aE3NROf)QfMsC2!v0mnO&5A3J$gjVh`=TsFjA_6Em z0Ts{Sqhs;vS0;(ID@$oaB9X_3_|5w<25R~3pqUklFtlQRtLPXq#96oK4#5T#Xr~YR;}9z5$;^p&R*n&7Q~HNZ?Zr$o{`tu9y5rP1DLqL=Mex z*F$@8E#}$oIK;kC@B)n)r*F-5mjfz|(rt;?lx{k*Ff|cDjZ}fBw~Dsm37P7dNImBtHacPFyRP0BU_M6lhjeeO!jQ|pnAuCsN z|I^pJwmqV?d{m|cSLb)>FGN?SSm4e-{~>wVuS8iOGbr?ApR-XGTttZSVtbDKT&oB` zyn+cH$r2ud_5gvyj}4rUkD9<$1?oKw%6ma&um?f&fRi$RTWvIjh;wqt!AlMnYt-ir zxHcX`P~V(&r$L8XJi3(^6i|ltTR?1>)-pkH2XCgCV1uF*VWJxu$F9BE^ttzLq>i>ZzMs)fEqXgFw9zR|nr2t*AMjX$WGJ6O=vm(#jXFhNbxV12 zB%#Vr8--~I`>6I;rRQhw?dgHbbS^Dj`eEcNIc_B6?~OMfPv=lx*Jv0NyurCrDET6+ z^z4B@6-q`kVgiSp`)-JTspsoS3z`Q*n?mT8XgOO|7LCy@$3U0#x7RSAvk?4_1h%>5 z5qL_JwMyoBO6GZu;QfP;y!)VKyFmof7y{yvSG;>FYGmSk#h7{125Kcoc!}n&;bF@JPu&~MOQS9-4--P+*P3sU^ zUTL1NTU`AHb>oV{r00AbcKVf@m->TRJbxthpZW9{RYVPb3H74rxn>3KAdA4y7zsSg zJ9-krMg8isn{QsDfINx(cw(Mv|-|DWJCN~AecB;T}Hq##45A_Jp6Dp za$v>-9jr>S;6&Yg{tmZ1622)oeSH}Ls|6qH=`(cB;&tvSY|dhZ&~%CK5_I>!w{38~ zWj~~lNy8|xerM%4l(%0+0itpV#f~gkh3&J>;iMWZDy581t(+oN{mjL6;6(n;WjE`a zqa?~XsQuHyo|eh*{lST~F6@1zL>(9qBNp0hr%#V|jiNo&1)G9&3>OtzZQm8^SLdWs z>T|9~5Rjmmv_c$96gvY|4E5g;8&vX!q^sUP^_7lf<6jW$zXaDaKQ31S-L0yAA(m5* z6!Wy;gaRvHQ!qe(_v!Wwc%r>Zg77;r_|wI3=k!ngxrKsXB@7KYpbS#A_90MR+&;Wq zoUrhh*J!~P=XC{X>~i{;2n^ihoqgBrn&K``LCQPM*-a`ss8}&qRqrBjp?7Rbqjufq zfpav2tJL?RQt|F)1Ac{&<)G7U&~OC6qm#)S;Rj|xm3Zed2}X&&hj#733uXp)14%oB ze1?`{uVg>y?H@GdpqcVRRDLP*FKJJrh?}_!%7!(_RhQxj!){S+iM^mFQt6HnDq#C2 zQ2)$n47=>g20-71=EVt+&hqg~jfi@2K@#=9Z|LZ3)}@F8@N$`0Z>)-Q>xIfZsxdxc-1H{YUIWwI89HYlmc z^cn%vGwLDP+j*`Qok7YyrYXMB?7Fmf&MpdBdJ7U}`kP7$5?ovEsyF8KTE6CMKZqNo zu%FWyRqB{lk=!sr7;c^6+L)qaWuxn>7B)+c5!96(pWsr{G1$j-TJvl4Bh<-=NopUo z1|84;1b2F3AZ@r_Vw|dL8>mrn&ENn`0>wf%St?Cg>RtDx6Xa_Cu`yg+2Co)1#7s%H zy)BFZCbhs;r+*?bfQ?N|Jd`~MopYNXOX+>ObZruwEFkQgJvS)CJ7mCEMbnpq>@tX_ z^b7BOYhI$+A67cq&?qF)N+6-o9DISb1O4q50Sw5w=HNYuPO0A;I1CZ1(Q4O<32LW= z+%kGLp5#OAc%-wnRQ@GC-LrSa${jec%@aHj+O0*W5DD3NSBq*rEaDDqZL6R z>xi%KS;Yx9U1gE7GYzs()0!0lzZ2gFB=xGBO#=i^kvFW`^eL0;q9_%=JVrT;GX975 z3$q!l>o@J#S7f}Gw$`O&ZTn9GucbRYJgA=CCCK1VADBc*a6UF(Ji%2yn& zQrae3i4UmKbkV~5-XLE)8K6(6cTeTk4D1({`K2t)C(1*L+58SP9@I@?$b8dQ_B0cX zp&AF=@|Yj+k3NNDDaWq=fsN_{s^p9dvx(-&WC$xc&pAyBWM0)Ek=TK#-Oj9k8%>7&K<>9)XS5stPtjs!)siwsfJl>K;-8i4tv|U@wG}D z)}AyrDw}L=T{|~SdCn0bkHSi?n}=;=2ahIc={8EQF1tv0=;HhJu}WY&}IN{lyx zF&KEx87jOkE(mm5Nq{N}Nv3JQ{ZUFP74{EM7SbIU_9FWl&S7bxAgL6l?oyal6%(Q# z8Z^YtrzQA+S6q0_z>VTnY{~@UxdakZR?bP22P$_WrcQ`(Hj#l2t(kh7#G`X{|!x-q{E-M^)@|H8@}P>gCyH_sh= zG1^w(azw(~;tQ!rs#F@5y_p}^D^G8=Zvi#6EPe-rADD>{-a@M6HBr^rv5mjW>NTdYq3E%7TV=R4rvkbycU~szF0B3eJ8r+x8kQTMg*&e?&rfBZfi8w5pqXFCe=!<8YT$Aoy=DOz55@jnB2X_IDP^Sk-OpwD=Qe1f5c_ z+zTMZ4{Dw22B5$Jy->|()}KPGY{lGMP6Z}7NYKLZoY!EiA}a?fcb!3!_S0@hj-j6~ z_ey~Vt*NQA8VN2W%S+p^B~o8xpjWm}W}5C1Me`;A7;}9mxf2MCAC@3NF2Xpk%(HSq z`jti8pfwDF&`1uM!J}!o@`ieVU>7-vg;kP^2I8@qu zeDyp{nPmKtCGyNjf_0$(=(x~GE>FzNeC)-ljD9&?>Ay|qC`32wm_H9`QCyWg7}-0o zTa}U`o%T(zcv_#tu}uea!7*I-XDyDghg9jwA)&Q}xWzwB%tFO0(C^Zxmex&T11%GZ9o*2eQKqnjbGgGPhQi@Vd;z%?J7DUecm_m4a z_TX{77(+}_oPgyW`5L;CW_V4#6HU;(%$YI)N?E0o0 zZfp$AmHFr5$5Lz=O=XyI^C|%iY7<@btSQuR?7?lhdl0c2u*aZ6S>{8!q8*8vS4=E# zcRDIsjzSdN+PnU)V>0eOSD!s&2Oh6;aZ?EZ%Fu6Ko2pIg zUpCrlz)j9Aj3;{&(%K5He+|%rgpuSH69bPQg#q3ZWQb7&irbihQ1UpsT0i8?*d4(1 z+-#ZFf*9mLypKC%g$_xYK5X>^F&!yxlbV}(UG$MPDuHauc#){q@ z;?14T5fpJT{jUftX7??P*>qQq8<60!`D(K!6~C$krwPKU(=CWRI)bKb4bP~ z2#XL;K^rzgl*WT0Zbk9J38nXu8p%J|L)*jIdvajvUXD80Vu+d3dSyuDS2?Bn8mT90 zrTYayivY>+Olz58zg48sM;PlF2OA|eOI5aXS)^eUX?VyH9ER;h`0XX?G#{>!$lc$l z*%^Hhqu(nRk|bg2x?lhgdeR<@(yinpX1bf^eqUcrPg!!-q${0j#z_pWYn>|__xtCQ z=bIf*fd$Zk=($L*N)Muh4}Tf%wIvuXH=Ek_%+d@sKoE35MvE0PDyUg;MdIWs1}x2pFQssufD; zL%4ZCpz<%ntn>YONEp_-btt4be&Bk5Hvl8}k4vT4+|uEBVhtt)CDlr@;C{HEXSuupztq*=k1l>& z`?`JlUG<7%5EcYdW^Ro&Y&v7p;mAy8VUuhApvjk1TrtiRLK&ta@TL?~TyAPyG90Fy zu%w*8?u$Y>%%*~VjLQs&CQk=)8LPx~O zT`P*>HZN*01GN~e3}c@G;)5JW>@+J7Tbz>;R5}V!NYwQO8rpRVJ$1Fb%mo*I--2kOzveL249jWK8H-<@2Wi(CZ^K|uUL8>F1ie4{*O9|MtZT-y5>FD)!y1TzaLK~R77 z9%bFesKwZUng^>UlxQFde3A+~7_L4{u|pqsS04%G3f&Vhq8z$K#Fm@a9 z*`(RdLl|*MdoJ5z*vmox)ke|Zb!a~|s{QP1k&}n&=jSywCQ}Fn7vfK?sywHRlD2Jq zbYEx8;u-pnzm96m27pMk4I&IKgPG;fho8_6dJ3)lgWfT(qxhiXwPxn6s+0TVlF@Ho z!wT$Msd{i?@}RC5od2mD4=H<~rFa4)`CPt)tY3f)azfM?qX4@l6pa?`txCZkGZHkM zP6ok_N!(Yxj|51fBS_2$nwe8tnHRf$F2d~Fo=dFphtz=45g`?~of_2xB*h;v*bi|- z$kvWX4N^c$bBezJtz3O}zD-IaR0f8O2GR&p4Ja@Nu)3X2?0bxnZPgtE&j^Hkll>(O z^Rlk;qBccycSn|k-FN|sKNlH(viZC7XVH>)AfUN;;M(3!m}Q!_yu>d{qKJAnp>(+2 z-Lgi4rB;zz9*ucn*{EV0aH&EM)&C%(k4NLfg}g3rBlaebJIje_(vV}vt?Dw<;Z1cD zbh_~$XVd*Fb|u%iKqK1@1&(@BxIsBFEnN%&52PQg`%0eqyZx5FV*F3#tjg_Rt3om( zd4Qc$KwFS0Us(WHSZ|0moHqs31y81Pj1u=b$i1UJ3bf+|3w)l2&@+|ln)h6OdhjqR zu!%9NU`4xQs|S4#P7P)L7}@TsH7#)@sx+Xe8dyHlla6MOE5s27NXKEn!9)C|0t12R zUPQ-{11aWa1Y?9e|rebOD5n+~)^~Ry;{yQ%D_fF@fuWO-?QA#Sh&`@KMa_f0a#U_kU zkh`4ogBANze8wSjbg2G>S9L#a+=Er(%tICp( zkaD1}2~iD1fqucZejNMSS0TUob^)>&=BxkMfWG*@uW068W~0)&m26JORxx5nn(|_dWRTeMZQN1 zy4brHpOgrtMARJC3bJ>G2k)8d*z6KBp-hql!8fxzoaa6p>I*9^2W2+Y`^S&W-PB=l zzU_S=iIb}E^FBe|Dthk6ALa;bHZNvAbYhMVxwH^8nDxf`*D5~^EIU%vJstPgF;o72 zGZ9IuV2!jlnRPC`fI-%Zw z;EC3@8_nQ}eU={Dn`{@Echu$@dQ1lNi#hKuf6_aGEM|y1*?p4(zL@w}J$=UTvb1pC zoSn7um0G^v=;v*%JyKEsX7R5_C`vV)X6oQP9^O~3Z$zykIIYBW9pR51bApPB^36xQ}r zhAL3^jgj-Z2mSbiPN`I&$IJP`@_ai`1m^hsLY_zQtKj?*W%X>BwwLtsC+Ek@m{q@) z6#!eL{TKZbg%8OOGEU-91+*e`+0TriRW^^4d-tQK_+CGU6b8kA)}4) zY5P*)IwAqy++(oslx$x%n~>GgsbJ%9y{FujlKOY~&5sMqOqZDUL%~dIkp0BDq|yRG z`RdM!Jr6;!nK|Mj3d;EPpKp3KTwH?#`~kr^S)@p05nhO_Nm3X~buu8S>bvK1Ej9TX zUu<;eTV|m%I*~W7AkN`W3SnE(WIwNk7X|d-M6J1l6T0#|PqqUUJy9VF?2iM-Wlxig zqOL@{ws(Ci;=n7vveMV~;R*o45elf}aHw*$2t|^|)zb8!l7h^^;3H~NLSa3k&~XB| z+=58A_d)8kJ z?4-V&M!$@ZX%g9^74~#4BvaP*s|WRBB^U(BSq-TL)1r939!Y0Fvh=8Wa*%(xye8{x znq;J=QocB&@09UrCXa%g;|oDI!Qox>9-92&TWRLLLBNxuR}zAE%P_XZ>si z*~IX4K%1ewxPG8Xn4j$SQ|j;`h>0)5`m4FME|Giy=L?67T?20)5##=ZO^k-x44~(6 z`@~n(g5{K-84}n@UXz#*m2jJnqlNtj{$c|@J@E!uv~WS|WI7~Z95ssWbZkBi51T7! zgn_nn&YO1P3`j%;q#Y`a{jB$PW@q7gKE{M(qI=bh&_7aVT>5rQU9XnLU*G1jyO9Wd zg@}DVp|qahSbTyVk~~xxzV@8j-Ufmh(bgKPB2KY{|8&&h!DteJi1Uh%Ny>7=3=@wf zo0x3&R{sv*#Wdo1CqMr9hu@Z5n3AjSegzd4VIx;qR3huNcS%F$BkwGdKbb0NP=QGm z3|rA1MpUoruO?a2dge|48{&5sP$RY9{#tsFD2e8^(sfhn7r1^r6A72-YCamJ40U{x zqD048l5a|-LccmjzxHCRJBNU2akP92h^G)2qxl~NS4G)*VB$ZlS%l{2Q1BFKd2uEK z#oTb}*JFMAhX+;Z7AIZ~m@aE;ULJyhbQyAa6rp0AqxQT99fY+l|AK1;>xSq$l`q3l zS+_juzPADYIvE_kk)sawe~KbAp1Ia!YF^zpg$uu#K@K3n znqNW+(%6BEd43ojr*KDZ-fS1DIO2;u(I*rlQ7U19?8bUqPv4x1_3jZ1pwU2WIN>ai zo!?_V=A$v{&OvRh;~=&43>}pm3M~BxXKl%2@e{#U&V8iaZW<`e1R2DYphUy4kVI(p z5P4fx#H=&1ulJ}h+yW%BiUE=pB#)jN?vRHVq6gtAYhH?Gq{2S})y?F=aDUhTtRNC5 z-s3raG!Cdr*Ap?a9`WBwoIhI|ns?Rb^lr{KEAy@Rt%3G~@4fsQ)aK0q`b|sJ0@7Ay z1gH-jeR;>5LNZ4w93XUL5UN);tDm1e>WNuGg6KuIis9H-QX!(^@;)=jF((5-&a3T} zTfWCDc&+NU<>5*o^hr^-jj!DWJMjX~rA)5lE>kF4H(gC@;71cn^-#8uDEL`QUZ536 za7N4FZSB#O>q24S9!OpN{?yKKvinw^0wS+(``dd!qDHuYk4LT@pV?Qpry8b<8h=M# z_$GNR1l>!7pq>mrh4o1&Z|d)l8)q21Yfy=%qxB*h)TOL`;%;9V#-|kBYwK-2+0aU4 zmw&p8Y;MyF9~h5?Ql<8zu-PN+c=j~ln~ zHyVtL(9=yD&KSqkMF13OiR=B7sy44sJ1`-I#s(x9T8Pcb-`FCfG{$9R+ukpijDGG> zFE-t8)>3yvD~9ta5^9o~3QR-0g_%GK=+@!iI4gvG?eAcaG~l2D&_VT}r{K|fw_;EN ziO5KWBu3Hp5>^CHS;TluV(FtnDQu9lgQ6}uk7UL>)T$*O{7Y)4t1Z!Odm5`GPEN*Q z99(h}$UM;@(64Mvw;FZ6_{D1~C|>NE-&JSQ02MjtISQYzKdqCB>&tX?790efo*0fg zO6=Ih+%LJ9ZF1pVUw`2U395E+IEDE+;F+mOGiKff&G_)1c(yR5M>Wn~q-=Zz^p91v ztCy*cZ!IY^_lX?H8~K%xm(M%5NsP!;r$q}Fu4jn7(#ud+6pcYjYr(m0q}C<~ywFO( zt~|i$u(|AXWI8(R@ruPLrt4j3!_Cqu0dHFfiEyN7k>8BLLM22;O_HgYZR3tHx=e}K z<^ghyf`-W$V#G)y;CP&ly?r+kbpi)98d;nlaHz@2I&4KG|0dnODUKIll*P{RO7GW=K|O}HFu z|JMjxG%K_hC9f_tOc+%xmoqcsl-0R>+f0HG;ouUUD%uiJ5WR+wFpl;_tTK| z+8zoU?9gX->j+<5rNN8sly}R7ndW-0=oi>CMQkN?{O#agcYC4CY&|TPq zmj&~caG*?$!mu042nw$aY%V4Gt!3OYKh8E3&YSb7RvvAi3sVr6!bCj0R~W=94#st- z0W7S9=*og5omw7AZsu8*o0*R6*Y-I}q6TuY?sL1wyYqS~*Is7Ipej1W-@K~}v?RJ$zjyh;r?I60(meU}OCfdVdwQ-TZvJAr}5C zdbG9{$|4;CK?N=C$GK*S_Ai8zsw8dU*x;XX2BalYGd8p>@@~ckx>A$_PAF zvo~Biq3L(KLK8wmVa2jk)=yI#rgjB3O28^ZJ6f!^NiIioSn?kQrd|v&6)~j9Vg*{6TiAg(l?&`{sTQ0&i;|-aS!sy9v3C z4yIBC9>>kLa=mSI(Om*2Zu5O2MDHj=T)#zU9l&!2ezCIiOz9NP6Wx43m_(Gtr{L>; z5a_S&Ag7|O&JX@^UvI^Ik`24G^n^aF(kZdH-ETMQq7Vx`Q)2t@*y+>Q&zNVxlgk$% z;|z^W*-W0tmscK8YO~&7)>qn~h3q^!k`c*!59GIKyZmhU>@4e$%(t4ePIoRXJ}U1w zQRUrM4Mq9g4~n&?mLd;?AW2oZ-qaU8grFl$CiC=#QE%G7>U&%P6#C{qu5l_ap>|gZ z<48xp%PCH^^6GrOzIS=PB?#&PE#i!@m`F6)Y1wrswM*4kVC-rLNZN3is<&x$ zZ&>0eM+%I6$4jM2{>|Y>M8xd?vOqY3IC)WgRJFdvVaGwe?zLQPU1Rh=gxjaPaTLW8{^|tUs@^b(}?9O?~ZH8gM{& zJ9%L#21DmyzS{pf@8zx$icV|rq(^65ZFt-f*AuCWHw=hLbU~h#!4TBKkLSjMy~yKm9F65? zb>rCe4~oGr6;ZHMh$5}*T}$BDi5GReAkr~Jzq&Y{Z75yNrd$!y?Q1k%{jwJkMo5mQ z+yws$spIgT|MPiOx9r8SMz^T|)>D?ePgBKf0c#0aXzz7sHwK&1iUFq?62KuUBinfQ z>rq#%3CL~IA_fN!a$XhzQ=!K-IkUIvI^UUYgz&Zaz3lmJ%}Q1Arod{=P7$#l8eJB+G9R(qhr`})SjAHS=RP=6MpRlzKeCl9kP0i5MEwdIy^#XxuPY@D5z83HI$ku~YmF~vO$o|QoosdZ z3P_W|uRwv{&VjKE9_P_40LKXN_hAgn#mmzy2f1f=HjdRlg5@Gu_$kT!`>3kZ?32@Miq%2#tbi4IE?vpzos#n30I^2HKQv8Nj2ek3oz zNy~bZl4CsV*84o%^6f24RDH>We&T!|D8yyW$hUbY2bk*tiuV1KTV2djPkDBhKLV8} zbgte+ZF*#yc1{g5ny|t838z@>!0}sX;S?m$?1;=D@4M8x_%-Dxoi z`&oNlw8ap`5M1Esh%%1Wx)}1cbg2F%BBz|{{ecsutWOi{20<2a;Q0REl}>}i0gjzB zLrq_4G-5*S@c_-Uj}fGKczH@Ei`RvloTpoW^7oV&^ojqUWMiUbBrHy#-eKdj(1zo3H${Bzey`jigyBX>fdCC!BhA>fDv zLHHdCHXxC-w{E(vw0J(IIE3uKU*LQseY*nw7S<(|FBJDk*meh z-U2EKF7z-PSf&~mWOsUuI>?d9sXk)&KX_feJEQCk3BV+2P&(fG;K%RXhe(X>b-(S%oR>A5>9g$+dOX#2|jq1?N0g3&`!N^7LqbnR>c*PL%Fjm!+`&N;s z!U1rI*)KGi8q3;f|J;DzeqFVk(;0)&pRA4>6^`Qs#SZa|RxE)CE|Kgl=iyF&bU#A! z8QcA&{$2@NEsi<5BEJh~|BgorU{B;Jw1!hHffcB8h%6c-J9yCh$HTKQ0)EIetSLuN znIF>OVa}}i64_Xto~MbTc-<)L*r`{6(s+FvgxcNPz@9Qhq9kQ2kO*!i*eR(P3Nilb z+aGtxTQs1`Spa>iu$j;Qf8C<=R1M+JLSdl|>zIRw zMd2ze%Jp$<2ZzqI5(=t2vBw-IMN_A6n>m*G|6;(cF|Phd0NAm++eAK8stu^X)aWg1 zudQaQo4Pr2jwSL~$yCC2ykwSqj}(Ltv?4vg2K9xSO@^%H!r#5=!^{qv;@0nkzlWOd zN3#Czq;`t+b;%iSYR^nGMnH*kAdUHEUl?!QE#79r3R<Lkf-CXfy1uH)}|e&N1g} zL=57eH^Q49Z(hd>2-OqwS`=rHAW1sT44S>zin$S~Me>6wHWr?SXlxPw#5cx!?PWwX z_dmjwka9x&G)ff4NlDj-f)2hMX43$kX`4WZsny7nU=>;7CWP$);??PK3{RC`yyVJN zE0*I$u`)m7Xkhh|O7k>A(0C(KL0xfEMo!bWf#Pb6v?0L1P#d)^fZ+(KoU> zzyz>I3k}yuAWtBgdj?#Hlgz9Mz>r43g>n-!5GJyQ?99%OpgUq~w_yoKYQJ<5w1BKG z(n&z~2UTL%pXBTVie$h^ahiamkQWA#B{(w9_#M}5lr$CVuoAeIl_*ZS5@XW6tqIyX zt!&7Z5z%KaTD&sV4}z)9$rW0_?f9qjH?;Vv>b43(4>$84OWPZ4MT29&qvhJ|;7tt) zzSVlzWbw2hZ+OhHaC{xabM*L7e{ZH~-qc!aD5@*Bn47OT!IC1{1=t69(5x6NxH}Ux zpG7V+ZE&Tnl6iIT(it|_*#Ot$_`+Y`f8-D6I~Kvzq{JoH(_|_`(Jy>W@tONNeaH+^ zfN^czJmY8huol8yx?d}<_|AnSUqN6hUO+#+Gcd3TNx^kFuX$J18-rN99#bkeQ)0lG z9Iw&rv=AgLXcNA7A^&F;AMPkEjOxGrBW}N=hTvbJ?zKfa5sTK{zr2)1pmP(|8xA5e z5pahFuQ*|^408UnNL=&`;cM!D#tlhiE3MkoWdFse2;0Y zZ2fFB=?c&n*cIcC+FWaLfuGxLAmnMftJQBp$?-2X3a_k6j(*K~EZ%C7AM*J0ecJR{ z!={~vstPRhKy{q2heh%JdewS}7#O%0UL1-xbFjZa+Qybw3N2bHhjaBW@1)dx-cYkV z4%@X~+0uRm1An$@SgsbEaEXA3Nq;e4=faH+Oe(P)zq`?->n8f;hMc1*%N5ubwL2VD? zerLa6&y=n-nYT{U8ZqXS=gx)nHY(V#u@g-%5|AnK0&QhVIc5$N>67_=l1S{P#DG9Xa zoL$C^B^O=QGUF|usDkWhVwe_nBJ?2eRZy3^uV?syC#4#6aG5Sq3T>{|%D)EjBFU(p zNtJgCFG@V$9~m?yNFMzmPrZC~KpzHH3dV+QE~u}EHZ1SsK#+~DT&q%*D-5srT9Rq!7@#?o0CN>A0<;7zaPh@GrT1=X`#)*m38vSKEbXC zMLK~J{-u1W%5uvI@8qyWg=;h=t;p=3dPnTofqg8pW~ywlDF?p27?meKIS_n7y`id% zD*}gheSf(ZI#>@K@s(Fi0zzoGUh&`S_j}lAoY8vNL0LzLU|Yc^2RpvQM{sjOxyTQ# ze?&)9z;88+^lKc8&RUtU?`42M?b1bxgu$JDyZ@`~yx-Y;zc`Lst(BTVYE>&$BX+E6 z6;;ItwQ5zWR&BAjnl;n~PSY>?WGq!GjVq}XGwdmCByz???4jEAcgeIC{pb&tB>0+!Qa zs9CcI87K)=+LF}YEcT$JK^w8ip*dF{Am^gKHGvPB0a`upg<&D_rT+uCta zYo!L28`3%q0$ND@&|he~>CXU)fsOW3aNJAetiXW35)UH-+F!<;nUIB}U9>>T#GSX@ zk)Vbv!Ad4+H~n^niYV;gs3RvI2L=ZAL*q0ka38IUZTPEpoVoJzbMr6r>2k~^wBeq9 zL|6K>97<*v`lwupY!twF_i#<;%j_Z>EnB^Mk_5?VVE;t6(O39=?FRylR9BFgN#zDI zFVBKdp$Jt`WTT4^SIk3gU_f_PUhd_(PMHG#T6eD@t^=i zwHu^wT;e{c0u7GbW*L0{g$0K-LdQ9x9~jX= zF27{1SdzFe`a<7OqeNrDOvCLT29?T+3JUKVpLK9R zvk_kt6URI+59{C|KEiUp4oeX@&xTO}k>dwUig0q0Wy z)h>!=9i2}{>Ei3H&z=z@U4b+3;Ai@=4$^X58=jr;prEQ-Xs4FyamFP4;`^ty?jg?w zZTR&#aax`QvwOAZ z&h%L+c6a5ql}(ps5*|BW3^t&Sc*Gy+MLZ=Up-Dnm`PWBOU-_!$-sP=aDP|Y#E)%bA z?=5&XFuM})reA7a^D@QsK8yVL4~@CQHCQC)0eWal2Dz`>{&R1CAHQLwbA$Zd2pHG@ z877xK)~1+4WwwQtU-9u&H(uKykg+-Cvf?WC~WV%OuB&Qo; z@3gV0i7qwX@zdBW9B%X6`st(kdacH<+S5uMzSrU5 z9}GfH_p+qCbK@T8K8SlKD=SOAVKCgP?ZeFeo>08Sn!Tx}Mv+Gv3(f zLmlmiQG68QA{JzBnre9J##fU85Tb_zxgpIgoktZRPFqH2 z>T*atJN5D+l(y4l)pPn{`E0;d zR~<5at0nN8-CJAl0dS%XUntso6tIH#08kRc9K+sPXKv2L+hbMW1Gv^Kj|79b#f%KQ zrs*sI|m|56|!AQWUw5anI-939HkUJU%YlquC(W(_Ohu=?0k~_ka-H7DA&l00^ zA8nsio(myo^>|eu&yIyTQzBF!lt?7ne0@6Tp*=o6-Whzh{ipp+vjxdo9-P!G>MI+M zN*f8U-n|0H$#U=-RL(M>fjJ3-Z=b)8VGwaK$^3Yfw_<6@2kQv>BXFx1Fz~{PSL3!q z@RgYntF7;&@uPxLD~Kki`I4f@E!C~_Bm4IQe>7z0zUeCDHI_M=+1ofdl^A{vk=rBw zQ=gVGX{u_I;%T73hLZhd5Kg71xsh3O;ez{&;!twhEyC5YtW!I=sDFT_rD$2 z$Nb-lg>UCR(vxT3lsr&onFVUg`!`{M)HjeQCi!N$v;l$_&#!P^4 z+B>IJPE-E%Iz8~-sD&?U^y-zfrD$l8%TJ{YlzD{ywIF@3dW7*Dd!T=Q{b_e-dKxb3 z4a^s^eV1#HPfMIjXmxgv7|1eUYJoHqlqyO1nrmP%wBZz2tZfkCEBm@Bz7jlclh!@YWxZyV*+P!asD%Wlc1U)-CzD}T04}2+)#($ z@g?45jjb}RGOz2&yW+3GVRloWgqHVxUS+zO7S2wmq>$8a=*gAK#mV-|KJPETkL zZXkbwehcZ3f*uxNgO1NWn2V%|b+f08&hN9x=4qeaz;eqqHR5?ecPz6SZ`#nWUA9dO zyTDV?zLgeVU4K*uqJc}<&GiuaHjf6Y8+MXfUz|4JKpQh+1#a;?XlE7oMWfcDgAdrm z(HHUuo<@B`mVsp8$P6}?F{+f~GHW()wD4~2Dog%VtTxJ4W$b3SZ-MF6bhnff~|K;V|~LOAdZ#fu>QyQ6WBKTmxVswVz4Y`u&opp6JM?;0{XX_ zTu`7X$IO4L%;S0k#SyYjL*qX7R?^?^@9%!pRr}M+?QML>x8`ZQms!SD$ZM7lF*XR! zmJ#TdGy<;JBN@Ih2s~m{A6_n7KRzz)11h_@Ivc(CFMhxe^1D$nLx>bdTj?*UPKA!0 z%)skL#bEzwNYx*|AkV*RR1j*Q{U>o&t9|_bbbX3k(r!y~`f=ZA@0%ubB#3$23m&L= zeK1+_yD!VaM{z%8)t$Cqwlq?ptT6L)KuM9|yM~(!)Yk1%0~DE3SYp9jmxzk?w0GX~ zkTEiig$ZvCmN)JG!6Jy1;j5@xXr}HLp|W@0y^w@yzSzg5j1SWm8v*YX(3+<<{iZ$G zj-uUf7)?H~#tim$|c>F?4uKDL6C>~Fy54C^b)qKOfDYf!F}mKB|EXAweSQ$Ntm zwK6seBjqY>DsaR;Y2wGvg+)mczRwA#X%T1tNcfQ-0!dSJ`@z^T{gjc0qnFgWY11hz z!4tec#_a0NU8M1BsItkO;hcFAHLaTW<}Po|{yzCf+SVslO^P5X*>V}KWI5SJGZPaR zfKEIDc9!lEsu6p*c3PA&?(}_0G!P2XCK2cO??jCqOz+@kfB(;25~39WifL`MVtc+! ze~{&2Wc*MZ79aq{6-Hdde|}Y`DxX-A`7Ro7)_7&Tme9$|5Mxe9uVbYGV$3yYFl%rQ zWTxcbuKW_ETD_xve=BuLh;Z{ji~1slrcY4NXxtZBwU*`kR&z81KM(V4d zo12^JNMmtj{|nBL;J=4K5Fpufx+~81xnNRp364Wtp|HZ$#bt~uadXA9c`?xA@0z)r zLhuEnhDzluH+RD1j{5|S-II@x8$xR4zsh8HIbY`aa0ADMec!RaFmh-TVGySWc|F! z&w5|&9y05?)MFgJGbZDCocH&RNf0%idXrY;!OJ}mu!s!`F;P$cnoa%YK|Bsf;RwJf zt*LIe6pPaks|3Pn4T8say%+a26k?T>&)NI6Ox?9WgRT2MsQr@`On3LHdV}$jn{*CG zo=@Oi#2?@>EreBFKXV6*a$&0|X0_pJrpqBO4IgoqjVge3VneeaOou|AwiP1a zAaPh<#=Eh;l?kV)=zqlp9m#hwuQ|o<@zVB*ch6ay`LNIwA5;wze0I4Uo)Q-b(OK^Yn&GgTjeV8b(1GJb6yR|Mq{~iA;!{dfX!9 zqKuyBXsH`cGNuQNQ=^1Q0Q1d_pLVICF8&T*U;3oO zB6{9R-o!!C$W$}tno$HUp}R|=+R$k{36BZ(BOYj8pXStbJG34O8nc#}2(u@GZHq<$i zrt|!lcqi+&-ajiatF|dk_3p@rgr12q=_Jo{3hln)hoTZ7D<-W|x^gan2*o)04Gz=U z4AJewfQfdA0eLM)9BJ|Grr3-q@XwX!;Z)2a=*I2|$>fDm-AchvFL_R}6U>X_~_8y!(Zeaa6U%n-LST=}`@Z13h~@8opJ zyWv@;l&9MweRI=4!|?N4X2IZx>iT|Ld+Ui066O0Ta`EJpn%#QuwW1~Fp7d)C-W=H9 zw-*{fG7tQa-9EwpkjjvilRK5sQMAlt`gY?d+K*K7;wWUorjC>a53H?vNrrdMnlFmp zJyG&ym(a>nttPYS!$<X%V1l$e z0%zo-+%&oG^|6RM2O#vhk>J9t+G6(-XYK;sRw0qRi_Sbe8umS7sr6gODm)%gSKHcj z{Wdi8dh*}Dyg_br^H5gWSB);b-7RJfR&Jx3i*}hA5PCUMK0r^UCT|()CcQ=@L3Zv= z-ja5IrISz#u}hzGUK=sFs&;&3*I+12{w!vC$p`d>_NcbDnJVX!-r_Bt#$!aqZ`8YX z522*Hv49(L?6-Qqt)cBiUJqd(rjKknp4s>?wdqRh6o{Ptyp2m3=@N7Yl&R3=JQ@H- zbl)GTett8k^SwT`%dFk>2Rh~#4Vdz~N^+y44Cz1|W^=9)Pt)D!0s2TrtIrBC>}hzl z6y~S!TDU~|RoaVO&Y;tR^@e!{2d1@0N2 zvNk5VU>0D692qWC!t@w$=fPII1oeL85c(Wiwr?fdX3G#grnl##X#ee?vZ3XBn7Gvv z@3CC0szbX~=l7%YVQcC2+!vu&o=zME@j>J9!Q(NsD(?4sfQM!NJWkoN9k%=Y^Y8|~ z1GAG0YGWfL!_#l>DhWTWZEWClrL*0I;&LhjP|d*?A_*LQR9(I_36p6yi8rb5%RDCx z(=Wh0w>=NU-mXqn7lZF6cw|~hmCx?-*p3GNyCRyr4n1eu*cIw)yS_k$ZhvvJ z3QeXw6iy2Wg|$;x6f?feaGbM~f8^LX<$ZIPYi5pnV6R}ED(vNIG}MnS{173|kSf8M z*waPSNF38Kz4}&8D*HCHnB--oX_fgC@xf!?M<1#VH5Y@o`rmvdfeS9q7KjXe zct~>}K{{T-hPOd0%8S+WIW|T{;vZDe+S6Qn`&bVi<&fo(Hx>M%PmLOca~soLl0}wy zdXr(9!3%#8j(I9Uhi~=Vq%C!(7bCmiquqHCPl0N*5mDVjB(C73zrekz{Si#S^wzp8 zJuUu7r>-xyXW{Bc!flldl#8YJB2PK<#13+7tcHMT#g1)wvTU>bO?{Z=3b(;|05xpp zz3U+}>IACO-~xaAUIoTEy`rIl<|=|PycMsYpXxHQcH0!$)Z|MS4fB!UuaCOis_B2@ zadx(2zpq*=oo1spH~3dbh8x>Qi3Q?Z24_d0mdIplZ$K z$oStZo&#vc5>_Lp*}FrQnmU^k)N5nQ8lYz;ih-BcC9up%KM0wT2plU~b-QS7oi+WH zbm~-lrLH1%$AKFsbXScr`=B`9GugIXX9S10(*eE*PxPAUf#}9 zwBKK-O!iRJMhhOlYf_^S=!-vQBJgZLTcr^1>DVfz_%mnUHN>gXCIW`vw>Agi5B{VG z-H99qrYj7n5pNLIfBIrWc+~Zu@{iE6Q*OhR=K6nf#`b?{ zFdHj3yoMqYG!BOhXZR2^P(|hTZ6AdwZG}UN8qD(YRK=_r(|db9rtoqziG!7+`N6?k zHhb=Aj|PFF`FVL4G&C`|l1rvv3MLT7T6Qbe!HXQ6P0>5*#g(N7l^U69tG2WJ#AhYq z>^+qm4_F6T-&@8p@je1ch*6vgM}e~ext$@wdsH1vwa3=vCAmk&4OVv>-UUZ~GbBDn z+{-77<%eEdrWpjJ#$}n%!m=pSp35ZECayZ15$$ZGUW>2`B}uuybqSxwRbA zC79RmusYMAgn{I+a6WoiWgwqpkIYp_O?kz;KzN{e@Y$IMV36L+Tf=bHMp{-E+xDCy zoS9=2Fr^C_IGJ(H+&yVoQZv%!V?Fzm@`b-lnjKJIT+aWj$HB5OdVh|}lxV9`4Ee5O zX`jraaz_s>@Ri71Oa+4|Dl1FKZ=Ls_Yh8lhL%?`vLY!U6Py2+3JGr;jMO&wLa#Boc zVjnM1C>GgmXFHl2J)KX6No8o+4npW%q(R`XS~BDA+7t_C)lxhv*hNfuPzJ_9y2Z_m zeO?FtR4(D6wSYd*gh7!*$?uG|C)ZL8s&6D(C|!rv{m;e}OBZN=uN_;*FxXk$T7mHI z*fN@eVA9lR|LCB+aG6{ltQ+j|_bE%ll9gyF(hPQVUwmPbk9MU*ILa95MCpnBihc6y z@!)7y(_pUXteBWSX}b=BHx5kQPE!>h4g*$KRPeQL!&#KyGU_WF1!LJ& z!|1kqq;+DoMV|a(E;FR;z%uksSRI(Xm6oKxE3I8$HQwwdOA*2rd1vTqFh*5Dk~P{bSg`CTa+YvAz`xrLAO5U0Jx%8P17w; zEasEf%D=7oe<}vwn68U!sopAd(`W4uClIHGh76DIN#q`@@yl*s?@*@>KTfS3A}X0? z9c0;O+tK=SkemfH%5SP0Jww2{j`)LIYDGb6-Ybw9Xq*GVg4M+4tzC%F3NCh9F}t2&aG+UB6#6 zdEi?wUKn4Isd=I5UbGY~bsY$N1GMD#LY+FEn(^#J^SnYBnaJpnXQ%=x8P~f!&`sS*%Z4M)Yu1=8}MZ62u+Dz@7#Vvxx z*tt3L>gN4L-Ze~%{Cxtd;X2IzZ>;QJu48`O)k^{gFZ4C9mWpkDsPI*}{6A~zgCg6U zEwOGkvHYn1coXvX%RkO~9|JMI<5~bZPKcOIj&W${#repi(%`=njUk%cD&Ip|HlMqQzK2`x_-} z(}2lJ{n=hY-M~LbX42LCcSQ2XN3z;oGDt_AYc-N|M+UjWLQ+qgPG>)Y{r{gggIkWT ZZ^TaR%+GyFvnL}x&((F+s#UGu{|^z+;NbuO literal 0 HcmV?d00001 diff --git a/services/desktop_app/electron/main.cjs b/services/desktop_app/electron/main.cjs index 5f91f21..0e870d6 100644 --- a/services/desktop_app/electron/main.cjs +++ b/services/desktop_app/electron/main.cjs @@ -2,10 +2,15 @@ const { app, BrowserWindow, dialog, ipcMain } = require("electron"); const fs = require("node:fs/promises"); const path = require("node:path"); const { indexFolder } = require("./folderIndex.cjs"); +const autoUpdate = require("./autoUpdate.cjs"); const { version: appVersion } = require("../package.json"); const DOCUMENT_EXTENSIONS = new Set([".pdf", ".tif", ".tiff", ".png", ".jpg", ".jpeg"]); +const APP_ICON = path.join(__dirname, "icon.png"); + +/** @type {BrowserWindow | null} */ +let mainWindow = null; function createWindow() { const win = new BrowserWindow({ @@ -14,6 +19,7 @@ function createWindow() { minWidth: 1080, minHeight: 720, title: `IPSExtractor Desktop v${appVersion}`, + icon: APP_ICON, backgroundColor: "#f6f7f9", webPreferences: { preload: path.join(__dirname, "preload.cjs"), @@ -21,6 +27,10 @@ function createWindow() { nodeIntegration: false, }, }); + mainWindow = win; + win.on("closed", () => { + if (mainWindow === win) mainWindow = null; + }); if (!app.isPackaged) { win.loadURL("http://127.0.0.1:5173"); @@ -30,7 +40,34 @@ function createWindow() { } } +function sendUpdateProgress(payload) { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("updater:progress", payload); + } +} + +ipcMain.handle("updater:check", async () => { + if (!app.isPackaged) { + return { + updateAvailable: false, + currentVersion: appVersion, + error: "Auto-update desactivado en desarrollo", + }; + } + return autoUpdate.checkForUpdates(); +}); + +ipcMain.handle("updater:install", async (_event, manifest) => { + if (!app.isPackaged) { + throw new Error("Auto-update solo disponible en la app instalada"); + } + return autoUpdate.downloadAndInstall(manifest, sendUpdateProgress); +}); + app.whenReady().then(() => { + if (process.platform === "win32") { + app.setAppUserModelId("com.ipsextractor.desktop"); + } createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); diff --git a/services/desktop_app/electron/preload.cjs b/services/desktop_app/electron/preload.cjs index 747e7a4..8f36881 100644 --- a/services/desktop_app/electron/preload.cjs +++ b/services/desktop_app/electron/preload.cjs @@ -9,4 +9,11 @@ contextBridge.exposeInMainWorld("desktopApi", { indexFolder: (rootPath, structureMode, docTypes, keywords) => ipcRenderer.invoke("fs:index-folder", rootPath, structureMode, docTypes, keywords), readFile: (filePath) => ipcRenderer.invoke("fs:read-file", filePath), + checkForUpdates: () => ipcRenderer.invoke("updater:check"), + installUpdate: (manifest) => ipcRenderer.invoke("updater:install", manifest), + onUpdateProgress: (callback) => { + const handler = (_event, payload) => callback(payload); + ipcRenderer.on("updater:progress", handler); + return () => ipcRenderer.removeListener("updater:progress", handler); + }, }); diff --git a/services/desktop_app/index.html b/services/desktop_app/index.html index 16c574b..c8bb5c3 100644 --- a/services/desktop_app/index.html +++ b/services/desktop_app/index.html @@ -3,6 +3,7 @@ + IPSExtractor Desktop diff --git a/services/desktop_app/package-lock.json b/services/desktop_app/package-lock.json index 9aed697..b992530 100644 --- a/services/desktop_app/package-lock.json +++ b/services/desktop_app/package-lock.json @@ -1,13 +1,14 @@ { "name": "ipsextractor-desktop", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ipsextractor-desktop", - "version": "0.1.0", + "version": "1.0.0", "dependencies": { + "@aws-sdk/client-s3": "^3.1085.0", "lucide-react": "^0.468.0", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -58,6 +59,314 @@ "dev": true, "license": "ISC" }, + "node_modules/@aws-sdk/checksums": { + "version": "3.1000.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.16.tgz", + "integrity": "sha512-EKnvkXSmz3IpA99tCNuI+dLFXyZyClSm8zns9sB/elvkU+MTuomAs6toJMPMBf98/fICG/urXDkzGz0/c3yyAQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1085.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1085.0.tgz", + "integrity": "sha512-O0xe8sR50AYkwxlvRRsV0qytEO2dtXQTQ1CF3YBBdE5xtVkbu27H0vGa1mjQi1/+fbYM80AWEIPai5jZmXyubw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/checksums": "^3.1000.16", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-node": "^3.972.66", + "@aws-sdk/middleware-sdk-s3": "^3.972.62", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.975.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", + "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.0", + "@aws-sdk/xml-builder": "^3.972.34", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.2", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", + "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.59", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", + "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", + "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-login": "^3.972.63", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", + "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.66", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.66.tgz", + "integrity": "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-ini": "^3.973.1", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", + "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", + "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/token-providers": "3.1083.0", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", + "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.62", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.62.tgz", + "integrity": "sha512-k8JJwYXVYlOOjWnPZDThQS1xDFJgi5Dokt73qFlDtrZAbdcint5aIdjB9XgJAAQVP5OoqcefQmh1FYXiPpvsvw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", + "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", + "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.0", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1083.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", + "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", + "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", + "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -2008,6 +2317,87 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@smithy/core": { + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.3.tgz", + "integrity": "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.8.tgz", + "integrity": "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.5.tgz", + "integrity": "sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz", + "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.4.tgz", + "integrity": "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -2957,6 +3347,12 @@ "license": "MIT", "optional": true }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", @@ -7751,7 +8147,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/type-fest": { diff --git a/services/desktop_app/package.json b/services/desktop_app/package.json index 148a935..f8a0d7b 100644 --- a/services/desktop_app/package.json +++ b/services/desktop_app/package.json @@ -1,6 +1,6 @@ { "name": "ipsextractor-desktop", - "version": "1.0.0", + "version": "1.0.1", "private": true, "description": "Desktop extraction client for IPSExtractor microservices.", "main": "electron/main.cjs", @@ -10,22 +10,25 @@ "preview": "vite preview", "test": "vitest run", "test:watch": "vitest", - "dist": "npm run build && electron-builder --win", - "dist:win": "npm run dist" + "dist": "npm run build && electron-builder --win && npm run publish:minio", + "dist:win": "npm run dist", + "dist:local": "npm run build && electron-builder --win", + "publish:minio": "node scripts/publish-version.mjs" }, "dependencies": { + "@aws-sdk/client-s3": "^3.1085.0", "lucide-react": "^0.468.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@vitejs/plugin-react": "^4.3.4", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.5.2", "@types/node": "^22.10.2", "@types/react": "^18.3.17", "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", "concurrently": "^9.1.0", "electron": "^33.2.1", "electron-builder": "^25.1.8", @@ -40,7 +43,8 @@ "productName": "IPSExtractor Desktop", "artifactName": "IPSExtractor-Desktop-${version}-Setup.${ext}", "directories": { - "output": "release" + "output": "release", + "buildResources": "resources" }, "files": [ "dist/**/*", @@ -48,9 +52,11 @@ "package.json" ], "mac": { - "category": "public.app-category.productivity" + "category": "public.app-category.productivity", + "icon": "resources/icon.png" }, "win": { + "icon": "resources/icon.ico", "target": [ { "target": "nsis", diff --git a/services/desktop_app/public/app-icon.png b/services/desktop_app/public/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9b3b1f95cb96367fbbdbd61bf5922704bbaef507 GIT binary patch literal 165484 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

?lV$|dG>U(!PC#a37-ET;FJ5? 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ȲtkZN!i;9{Eu28A8;Epcng*fyH=F}2 ze`vm?497-895IN1=(4Vo*EhvyP1Alp9M;#T&j#O2+h?CmEB?ZiMqyQ;(Gu{*$eS6` zK)Qat$a3Si|L*#G9VXnRP9F;CyUzoD#;pi#D`xvsCYF2lC;WKrb_nyrZgO*cmAkj4 zx5s1a#}Ej#Z-G;2c)bk60HhN3KEQU_2By+jPn)%k-k)kMv74461JKwU4sCQJjgL#l z22aOO8)>eXY1RZ6WkjMcK(kuLN;O=GR?4cTOf~zXv>gs&L8NTwi@7S7D@YQq$KxSn zS!~3%q>|=2SwCgg&8*7`uCEt>&ckRtB7?zzYQk7qH+;EjqPmu`R5S+8W5ReuD0&S$^)Nx0De3<02{;9@GH$%%i`o_ zkYAjRhu`Y?;)SlOJ;Ec`DbRpSm`)>6)#Pccnut=xFdh0N;ij?A`8Ix+rjC z+T91%#ix63gY|cvlWl3_{;%7_!>uDQboS-#t#6sScdhJ^&cUYL*yi}$ks4Xsp}X>? z_c?`|-&wE>d$LP zwSW_K<=#h(z~qZHHatRDRS{l@I9gUsn@~m*rIf5&H8{V#{NrR;{e5)Mys7w?$693A zF);(2OHpE8Hz+wa%q8zrLv z+O6I3IL6jy@L`m-t?Q+FKgStFy5c&P9e_S zgZ(A8#j&RKrn$<1{A}Yq_67+T=BC+}r^#-lK~_bDgcG-JEQL$3wI{&5aqr)CoJKp> zkmQh3Fe_UCiHWP=eV<3ib-8DA%~;}R3PrRiP+SCa?&Iu;n1ezqL!7Z-jos z?#pOB41z>I{0R{=Bn9FM5($vDHk#kHqOeO|u#7oWML^fw$^Uxs;B5$5h;00lV*?RD zVIm-9`E6#*+hZM+sk9*Zbe!%yg6_)wA*FM^#pbvT0I|c}C8aR+5OK z7{^JfUz{F)FQVn-=Cc0nh9*GwbO@!`+oBjIoQVVU2yp&fGx&}(==LB$)Esr!RPU@A z7G&mQ9G>eEp6MHy%gh+%IZ4cKln=JKP`Yz+-|h_9fhYjR4K3xy31H*}2tnYvxHKe0 zEAs;=kVK3SN)%nSS$e&i&0lIMkwV_BbX6=*r2aNZMs0mC{QQ+j58`=6o;9t`@J^(r zwIN1T)==Li*S$RszRNlAr!~M8!V}!z0B+&Uwf1<5uVyfSb(g?!(bu{*o?B&)lC08u0yNvUWNN(5IIu~x@Ee{82Zw-%zITxWj)#fy8!%#LXiC;(e6GW~CkZ{B| z;vE3fgU%UmaZmh=&IcNL|4cY&+`n(0%kV4s`R0DPV^c&UDNYMY&`=_M(H^677u_aI zem*#i&LQ36)cUt}5#*d|hg+{B=wXEJoe=W`x$}eMzf=IpT6?eh7R6f)b#N_=BZJXD zLNh1@k$^@5Ab!?4cknBZ(8;uZ5W(vgQ`yvu`0CxoQCY0=hdL(&h-RD_8mE-fNMpl4 z`FL^wZV&&cD<$M`xhjU!EdA@zaD1+-YNCapW|G15wL%qL#S^XhNyMr}5=T>G`1|KP zSg*PJ=F8RTMVb_cqro+60z+Zx2N{1L-m1&F#PM@R3tY2DQL5Ouh19pXOPaANFcG?eYtn;1U5Us=syBg z#fAWA9iRJ;@#h=co=W#hD#92Oahxo4b;Yly=P%o~wq#X1cJ4H#RB{$oaU9hOiQrX* zEw!HsElaC55~vW=r1!jYaq z4OkMG!v!1JPJ+&9?@J8(gA5&jZI8A0WsnqzvA!S_-m-&k`DfzN_5CyJ(+jMisLH4g z9v%bs2H1F2m;09&KMs=RVv&u%Jd4uLZ*FS%gH$xdhBvMna_!;kqNXiDv2Sejpv&q` zuG#;fQhqH}f=7yEHv4h|e#c7CKCHDYHcWYkw#*y;wJ^`!PVTiwyhBqH7LNCCJ`Aeg zy8JFNrEqnjGq7nP9_yIwT7KjGUB2Dg8*9kSEj&H&2Rtmc@qFC1&+Es!_LaUm^3 zR0SOecY|OBu({T=8I=pmR^lV!ylXZ;5ci5dktbr$~f%~x;4=My6syTcvFHOXLiE_MVs*+pRw1#Znb3V8e394xfZ^;29 z&_bjI?1>waVInl<uXfSmFEOLu@owf@=dqrWH7&|^~`)0_vR#cHd1Zz^Rxdv^S9!z{j$?=JU@zAfjPU@fxHOt&H(q|lz^ z+4-U@v$lf0EhT|w)J>Cp^W*EU6lePLJX;b?GARTz@&KsX;dEO&#L<6oLl>aWX<=^x zqa0wO|3o$L0xn=lVpu{76+xihLK?04_dIXPv=*?n1iid#ZvYJa%D?Aw$c~cLZ8Dsi zLr1`0N0$Za)bhepXa3%lmRq}I8wimjr1hT#UPt(zH&>exYqytox}SGIvJad;Z`szz ze1Ee zT9)cDH$)VrWfW!Ae7W4Ot8%=Nqxz?H2A&*%hYivSnVnVDAt$Wf=VV3%AlpB&`ge9h zjWr~DQluFGTL#|^f15HxsiLAN4~oP6*V*y@TXk{udAnR5&@=(UnFgf{_a-#=UM6_` z-TpIkx0-ga)X4UtdLIe|El6ha_JT5C$`(FlQlyxy=kh+{Oa2m>A0DljQ;SKg6y9xT77EWFQH7r7c0X< zZa{ZB?lu^kb9ZR}w(VQ!2&}!dv4Sz~$XK_|P_T4RHDK25QUe|h*v!L|Ee^oi-+mr< zJxsg1d(I8v`;Ft@*f-mG*5&N( z0FLfEqL3bP>$6*X=6DEb=mj&s?YmwX(B=Ii34|kT@84fvS$H4b+BiBFMPz==@zG7r z%C9{fa;A}qiTw^xin)mA^NWLWH6H{K{@S;EP=0!Ws1H>mGvisK<~EuGB=pUfF9z6CAc?1K(~gWN zN-(-$KxV*eaq>8 z{}9QB<;V`kXh6b(r0|NLLPF#6v*_}oD3-&fuJ@b-^5%IA4$JK=mWgZ4R>kUbA-)~H z%%gv&k*1Z-_XR`@(YYQ=Unk~;mHq2xb#tG)jXt8?Yk(qj8}(Hh4v{d9fGf4y*p5}w z4T6U*4ZJ@4+Ny!uPJx-nF=*?M03&*5b$-^E+#8O;PlohEgMZuXY+pWHX4_f5>&$OP z#6M-eU3*-2EOTGVVH*nafK3Y2!R`^EyY_6`9_@0y?j5cKutR*TUv0mQ#y>L+fI8vr zJJ;GBy7MZr?jN=PE9U^9j=)brS3bxHcECB-DEtFN=_HP-7*sW1Oi!A!Og4kBjg=52 z`7y@o!=vM~{ex$h^W`d5I7xm&Vt%gdYJ$+jJ?l?4VK<@J1d^q=}I`$Eh3AWJLc7)q&(-LUs|-!j)g zhXHBKX2-*L)qZY!fLUGFRM&8d9Ro!U5+bAOXI1{NUw|tOG^^P5RPSh4VvL`Sf^kbI z96YB(cooMjOW5*8UXF}hSZCa~ask418{#95rfJ7-U;pi2(!um?lSuSediwQ271@9v!ezw~vQ9WOJjp!rc*TxJ-Zonq4)Oiao6zCtzdf-S%CO)N=>@ z_wBh3J3#jKrUQ}eH>`EH*V*l+Io~GI?1<`)sA#+dbBc_U;9*{?R}rjm^O*fxyG+*R zdD%n)^aD-YHYeiVSlAh%_p~;ljNYSH@c^^yubt5+u+g@G;xQUub5nbmX;beqfUvsT z^mg6J#t5@OotA|y|FdO=U1_4-CbIwY#-srH&X_@K@68irPy<);h_9On011Gms?u=@ zehoBy4e{#l<*t=GwpRx&CS&>olTCYZwftHLm3Alob`;+o2&GKqgS}ar4$A4xO{Nuk zJ)h3_qz?8c53O}0MDli3?J+{Cal)^OR6`JLjNLX2Kr9s7<4m_{T23hqRVeH#0(hyd ztMP?s^W($)ALGGb-rmeVhq{h26|fU}b+_*BN6{K0bOKP(FVhWtCa$1?7|JL%d;lxv z3HR`0c8xIaZP%kH)i;nOAPy%oe_rNIb6M5poTRf&j+PaE6vKfK9b@! z$^`zHWbIt@<5)$*L@1UjfvPL6xopsaY;95v_7jKijIe0}gV_8L1C6RL&v1SVb#>oH zASDp234I4QI;X${Y+*ZS=Q#Kjf;Z;D4qJ!IZqq2`d+6C2a7+~#htcGtEh6B(rEc~Q z9E;nzgNKXal_;P+g^nhxFZ z+Yg1@eDr9O54|mSADB7ay@}+=H3z^LB%dCz2B32oT*|<#Q3jguVQIp~9rZB8Wb2iE z4~L=)o435Zi6ZQ9ly~jwy|(4?&W(w_vHgK51q`!3{$A`mj{Ag9|4jG1mM}sOUu|BK zK2_w92P6QR`_DRx27$tgK+OGf_y;Qt?2Y_OMgOp&1MCR|QfA;)8 zjwc7;u|s1M5J|7=c0A0p<%=|H5?xnP>sVpMDPy2k)jqG=ct1_fSCka>vUus^yj#zk z*5rJ-+Dj4{pG3UDM^0;D0EBaF0>{C`Ra&WBwnZc%Oy~&Db5L^AR@HDYD_zO%)SvRqFo_tef-_h}o4Q=d)xfM`Z5FG$7{_|^(TjvTLT}0m$#Bp3D*p;Q4 zPpZ`-#`$L3J>&u(zb3uEzkhyo`tsefjz)D=4BEDJs)4_%($OzEo*aNjjKCw*t(1qC ziy}E1P3Ge`y%w|S3914j6IIqtN|jWnd!tzu$K{(<`5cLE!KylDDn8fKo9P!UNtTo0 zV44fAL{S}KSsz4^h0Z%}k2kn(C;C0>OmR3Y@J@^#KnyK>rY@mY5fn6q1xdP`c{jT} zcm$$ocIzmLj#dViMSZ$baCn$~{r`=Kdfi<7urGud*mHO9OyW5b<^gOqjKy+3YUO{= zCI`j4$>@(WEpxi8^@+v~GR(C~?1*xXoEIbqHlhFfCSCvW{iez}0VdySqiusZIH=Xa z`A$yNfF;2SyK=F$aM2+EK|T2zXTZgi+YkyL{o^i~Duw>N?zeFO>}w2=Xy+s-L!#=- z41@Q(G(=F-_Q7qzU9Q(hV0gBU@S3@|j04~h0Dm4s8?@t|dl1bbIQ?YP=|Dqm5CC_- z`DzrDB*yN4z5&ph!?JxFoys4sx3L01#;NIe0q`r0+m0yCS`r(cC5*y`Q&n|eumI_k1MoC%4UgVwEo8ieW^{eP-{uit%4_+8 zD+Rz~jmf2m5IzJlUH~B%jHyPTQpc^8*%)3bwrB;vWb8Utx{ccQh%*ilx!n2YED;!q z5L#Sc#5MQqdW36K1b`Rv3TlYqdwv2-ngCbQwD*Z%$}FJC=Nt8z6QfAuaMy|}!p z;E$@M5k@#hYS_6TKJv2Ihl`4Do%N0Lwbt=(^U1%9n&oF^2eISK;o8mG&Dd%QtPwHE z0aq4i=WlA73Yl8%4)C>(Sa(kS^1ie%n8dBm*6*`5?&y5RX5UOIGea~p9) zOUg5GIj1irFZkg|oX1>r4#1~0o*aPp8wRKrLeTSKmC`7xj*{pys>MLIZH5gg7-JMx z%V!zq#b}V9-4x}XauoravleFOgi`RfER&cs@jOo#MCe4eA~uckXuvwZGg9x=bJpI& z2e-w}egt5QrGY@9qA@h|*rWt3XJ7{(;1Q<;|D{|>tto8;Jp?w+^T{9oZ47WN<*P~4 zHn~Cpr7h=Rb7lQ9rn4-s>Uytf-;5*D%+iTm%{;NM3_R#DsEp+gYtk98g#IuL$t&OyM^?s`ysBBSg1yPoFx zP-6rAwnO?J1VQ;B-dNcF7rXz(;&D4+xB>tKoBrUBmks{thDEb*o+Ze!5-VCS&k`jX zdWWadMfFL{r|Ixud0od}T;9yTWMZM(raW%wt@F)yvf+&9dU<(ZTmEfp$LKC<*k=EvB=i%odB~*O!yg`Df3L|4OcA z&)V7iMVur+7*p7wBJ_y$t-7%;AKy;~T{P(`1F$VPw6Le<@EM##102ry#PkusWc_wRt|*&hlhGywkLf-n zz=i8B=dCooxw-n9FIUThliz$Nv#-yu%k1~9grT`A5v~{eGofO+gF{^m=A*2QuCiT( zW_ud;ghGElA6|*(jUN)ToIx<;Y^UsK6`O%FoV<{*W#6<_3f3`jI)hy*z!>e`69{_J zSX6)-2cqA;MW4eR?hP~^6K>Z-)Hs2f-9$b>XV~qs(xnAL1i%stUA2L6Bzk1Qe%G$c zmLJ;J-Vxb!4%Rf0b|6@Hp68EYbdQ52*mYt+F1-_`=WzH(sLSYVp;xS78x98OR3gI; z6Ns{*(U;7U0K*IowkW0wNZQ)^{~h;mm_G8ZxY;+!KrT~IMQjiw9U=%n%h=qx;C$VJ z{?X`T=W*;jst9)OOXH{jS>*G@^`I4E)Sc>$Mn0jaPZSO3lYqVzr`YAL4^5e+?_=!VFnJkNX|5esryo}=}Ptq9y$fS_5kcz}g#V3UDOUlUn?o}ae zAe}bt@ExUR2aMvQZKJUuA>}7|?>XE#HgfyQi3Bl_a3XJ@B^*i;!xbAEX5v3Jbkuj{>o`hrh?6!TU696vK!dX|BH@9aVdRrp{E zsZt?}w7mIl%G

C2lmIa(<;NGhTBWNVtVyOg;-_|5bso>yz0{84mUx%JNb4bKg* zQMvTFeRG%=t~E&ktX?vcyzNA1F$AYJ=)10l*RV7EHg#Zr8#vga0bRbnKa?KUUxS*x z1Ayp?G{Ji30AQ-XI0asNz*iUeA8Y*0I*c=5l-OLAfp-9U(V{i6B2S9+l@$K74)YGu z2;D{dzy|-38G{F}5HFY^0p7jx2halU-9-~RXtzE5+58+G1QY@4^8XJ#4iq$Ert_bw zZ08TGZ22fge^Sg7p@kwmQmca@`>~u><3(A&!~o$g0v|qBlu^}^;bkC9~ymTdxiYjer9k>goKep;8bh z$+BtM3^pR)wN80Wn|*hF`58{>$62~iQs<-pXD<-5Isyp+jjgNp@3rBDNiBFE$j z_Atno-`e1}qu6B1?B3Oxd7Q9Q37uwn|{#0l;*Ci3rgFaE?Xi6gV!yT3W!Q6?!Azp$J=k zuOS2-IX2)Pbmw#9Z+IUkVVmRk69=wJhLr#h=)xH6&6;z6$KG|wfWNm|R|aUFPv?T; z=NugX>;UNQhqaa6s)u#VRZsrII;6;~mtcdhpOFxMXTouFpTqaubjhJDGg4lcVi^f( zyA^FA@Y%DYHH{)t{8fBgMn zKE8mP`5$=GGFwlMuD!(=-CgbXq9>hgMzJn}?Az&BBmlv|OqcMvID>{VC}{!>HVwkn zJ4HdKez0-G>)TxWB$!zGl(i)?O3mnXJ$f->Utb-bh;NqDHwW`&i6w$qJgK&|>%8Nv zn}<|LF)WrBhf3CiD8CS+^z-lH!D%`d(QgfJS+y8tV|AwYf7ayCH)jd|Vr>doYZ|aQ zE#HU0_#khS%LcC>Lw~+4SHR;i>l}jH zJLU|nBMo*{1pdEm4uCV-Mw7rf0QjCq1wyC6R2_H}!SfJ0v7+^~f>AjLNr>)dTL0TF z)9rUywa`gq_Xx+WINNC_*wo851G}|IcT3E^>HhlfTKL^_b}a$aEgv{<{{Fi6PVdSx z!&?cz?@&724wmS-CO{nm$28Dbm>)y{Ors)qvp9@s9Vs&Rn>&x#RH%$y>)+rZ`M@Ad z_h{`t_?_=zc{KiFs1mNrXfc0#R2K8euJ4A>Kj+c%@bL7Tx6|Pti?ZF9%_>T0U6?8A z6CF^Uua!r``S5$@F?ZGa{fV~- z1zp=ZeeWEY=Nt7gQUN4x?G3Y5f4XCM5DwS3N^D;PuR;B>42OO?sdSkS;%hFZ;^yZ1 z^QNgg^}ZeDdsvvIqIlGj_(X9YEr_0tip6YP!wj3cv3tD0f7y_i2kwK&4pZB%s*6Yd#qCvLk%tFqGatgnz3CKvpo9M_Y z+&Pb8txc#|aj&$0@44w*2x>WQL?huaIa>|>knyk14iB4uo87$mvTDS0Lky(kp(TR1 zU^*dndUO5ycS*UNPY%EOUT0sid7XYO(0K3cbQ9{@f%j*% zf9EmIk&jq$^XL*{4@zxmrnkT^B@?hklf?7ZCP`_W&(6XxlF-U%@6 zk`Y8OMI;_F@XCX|B?+Uk4q^2UfI|X$INlmd)1W9&6hs~YB1daZ!Im__x`W`*1HTSm zjo@3>gxmw;DD;&MHl@&W4A!d=dI!K%Id})m%PIyaN1tx!Bknb0@3$4a*MGym(k5?i zJ9%R_I#;pNWwzXv=>qP%@0m@fbZse1_OK}-H2if8N<=2-&mgX4Upd%3;e=yr-YX5)E(!VgB6YN#Dj~k!cFda!uh-|b6nf^gn6`R-Pk(9ul z=Ktm?G_M(>?I2BNgpcDl*W_O)<@F#VXRF2eo)ojxNS60K13nq}AIFme@Bs%30STq} zRZ)+5mfW0X$(y(#drIj2rYtAAZS$l3z3;0uo?l;F|7HvMtwW!ckoi)`{B4q+CvntZ z(wt~b4B^Uf+}`6p_0Xi_5FiJ=0c>O3!m+r9l5h|-f-5#L=>UvOvAEt^46A$>uIw&X zz}MehJ~$C)hqMU_No!K1S>|!gp}8 zW7%WqV;lg2Xsb5D zcU|1#0Q7nX_Dw7MyWs#xTZO8lXw#?vu){kKMgWKdh!_Y&PVEmlB^pSk022A|2;Nnq zU@E~N3YZ4KVKfEcqZwT_1ZOH@N1^K^FKk;V1_yb-3wMz4gn}gFoy%O1%$;E=? zt%TTFQ2Wo?u6x9z|GPJcJ%ge<8|%s0%{LwvNCf$7!?FtJ^46WS8bm!m!8!-lIWg51A%DQ{Us!sG=h^cR!sj7a-nU=w z4Ez>kp>s6+9jCbYNF=DO!9cisB23pkYJ_A*_l(&*qkHxQ10rH<_lGrY7UmHQG#5Zp zZOZ>F!m%!oy|y3jF+i5TsW+-)&~0S+$>!{=5n(>GZS)S*w)NhA5B+tChMs7hq;Z*& zx|~kWKC4&PJ$%0rF!cRS@VSX(JUA`t)i8!;inQl)lGEZMW=-Yfe>U5&e=6h20r zn>UW|;d4mH4X|Z{HoCTH9HSDb2rz7Q>Y_1>?(se0QoG2Uu;?}chCdkj8_!7rcWs-! zh>)o|aEI0*1KuYSW%v@kH}_Lx2C(3c&$?Ys4QM4}@R`W_9QV-Pq1p+j(&1JLOm1dW+6okO%0%~`Po zW`sf;+y;*gq{C%z-AS%RGTJ<<6W}}nmPDW#!9=5(%}Ii4q;e;np+`tu#Q+|Q;Gl{e zWRuud;^yu3t^eWya>wIvSr6nsDn-&%T?~0Uy}FwJrf%e#u1dGB{m$w#(GX|~+^M9RG=fb|1GlH7FGbvd{H^it9R?#&j6y@+DF~A}oqX{+ z%Cs&*!`Rzn4!#@a_028^dHha>*#8--5(ekfJqvzWqhh$37UWoWD^^-$v)ifB5&;52eFh6)mU5c*46f0 z4tpCY&Gm9g+Ur8edhL$cbl|;1Fdj{=;?axp?T!3n+sZ5+rd2Jklk@X8&zri&=0CTQ z*H36XIRHP$=$En?=l8WB#>PWUGkkq<@f#SA z-h6$0{BP~~)$d4AjN?4FDuD`B0(;1@-;H%6wQaKQ3>v}GK!lSu`H4&?U;bV4Q+B!1zf~a+8;D(SVDr4B4(?7 zSwx2n1qCU_r^FnSBM;(_tt@ab34Ufcon1zuZ=>9C48l|5qkZp^hQ{7&CD;^*Tf8$( zn+}CnAoT0-7i<$@=$(WhQV)3nVOpR=7=qHr?$U*hh$SNW{r20IN05+cfqB%(`I-wbBu zy8~=6)_rs5+(1FI5tnolOIan1K|3C$7xkiuR;%T{mOARugHNaSKO9dEz|S}k#&24Y zyqnKY;=y40Y&<@bv)KzKWgIV-hZ92DEYB{hsvc{lP(S##H;Xr@QgKt&@kJCb2YL4D zc#vNuE6o&&-hiMuDZlRsH-=MIYanC-1G$3F^_dX|Y9bB=NsU^dML0GIf&K8LV^gwt z&)ES_3O0Q+7q z%3}zCn%cC0a{zFDLt-~N2oVtAh!%KtLcFGi7->m`-bq|*U1_e9RVu)H%{T^*Ghl!V zL$mVAggu|Yd^aGa+|DS6X^B2l^$v@wWBw<~^S(aA<&iFNpj*yRv~gbUr~S zMie?0>jXycAcX7i4nTsrc%ZPX6Noee(*8WRiQz&h3XzH-V<`-h2#AaTW8sFDppqIC zYfM#!yIQ-AwO9B%3`3uvqRFH#et)$>h5)PLKck!}-N;%2oMnTRtn)rCzO8 z&%P02kRKnv-W%rS;@#!%N!3OM39zlM` zK{)&nofs#88l1ovYi+J;ofoie{tJOfQ*LNV+zF5fbCE2YR{Br-TlfMgn4L;^H{1nUD8HO>Tcah8`IR(hcG1Tf>He8^_n_hiAL@7Xp}ngJihoJansFM-Dn;zt;A6J5kZIZSlR^ z4uF7|rG`ekSS1i)3tf}}Q(o?Q^GSJMV`EDzsb-Ku1|v3rjK?Nw$F9GcEur8QD1*|4 z>6x{Y;A@P4UsxAeAu%(>}^fhZP;=r+$*B06U5L_)e)WCQi1Y8R7MG-F|UlbRz4 z6;2+Z*oglq_tS(g?t4gof0FeE_@k*TY9l?6-#N8JIZLx9o0`E5HH zfu?x-(_%yNp zlISv8&fXr^brGyXf3n6O$CCr_GY;pCn!CIy%ER-j9;(Ucf4>-{uhN;Au(sWoS2w>O zN4%Kl=}omN_fSh<%c_j^vadj&-%O8nov^R+J&i*q?>7mX`vdkf&ijeG#jQ41a;e~SiS zH32#eh8LhTVEgIFz{wh7Gbv1se~?_V7!Ii0qD=1hwuBd*E<9~+#kNMx2{FKHnj)5 zjzLI9M0;r8>q&?Bw~!nXmH{OU22BDH$1~*CV_rB7=Z(u5?(440?9Z#a+6HAvNd_Z6 zh9Mak0wO8`8u1~5k$>&biZDvw+=Nr;z)Av{0uxvelY(NcBfy}8NNpEDR}I+h%%(K) z14d`1Z$D44subS$5FrCl-;K;$&Ut5=5G53BP3>`c`5x>Pe$HFYz*1SOO4ctzro zz8SID_mShww1fkB3A(}b0KmKG2r7bLveXQl2bs!sr)7W7Ik>dz zt!g#2>H-GpHgmC66WlcL+os0wbN#^Ox{KvL+L5<0`uVm%&j=deo}n6?Nd-{TE*230 z$`J`*CIx^$f?*ku#u_|X9n#tFbS@(j2l=0;-N897sJfQp33E3o^Dzqo^wjoCIG z3DJY=gRSii^YU#15nfJ}jk0Wg)IJO?P! zTHOdllzidmW}mJH?egY#9(bIA?tzQTv22A9-2jy{3UVIpYSX)x3LsQWCMT~Nx<8yP z#WU3?h%;L4jguc`sp47N{2mW8lnH=Ocsw}(KmV|C5+R70Q`w4>X;r6}hX?<@pQq`+yRIXI;J%+Eau#$kf5jt&_$iXNfD`!9Kk<~RD zekm19W{Sq9t&;*8E3B2g#SB zG(^Y^UydtBdQyx-Rm* zMr7;IoY6MUMoWlKq!2kMRhjZnqP66(S}9pb2a*1ohxbv9CkNmc8qQdm>7Z`f;oo1s z{nwK;efQO5{9nyt@wqOm=g$uge!LXoW_Eq^TMMnPlU#!E3Z=24q9{(T;^@b{{k(oY494iNUO4^!mu*XVorvUm51n;>X@5lk*TEJ0zfy_n#E#MtHh6>5^XkZXn z);_swBM2X$`2*iP5U*b!?t$CWPJvicgs|iN6&wuGEM1J!$(zCQ+jm!&7yl|X#K`iu zI}vqLpI%;kx5sA}dNe*c|LpMVR~0#(To>?V)uiME=|42e!;hp)}w2qgX=>FmGxiRc|OIM&8mi8A3Dw#V64LXgP(JQHYEYCeScq zUZ;o{VkBH$xj_m% zDX@#^oe&YC1LzFkItgyeA_wRg805e>3f2+8`=)acJnw+6I|t^}Szy?n7w4RGQNFA9 zG}-VF)(`sBCAxBD3T;&F3`#1MEh0(@*b2f&^T z?;Ukhh1EvrrA~u0x#b|b1Kxgsn-5$-B($_84Z4-s>;R7T*>{7eS-!fw`J-j9(z@-uJ)>?Jq#p~-?w`S&Mao(Ep8}ac=)c=fn~Z(E;`X zMz#}T-!j>$Ev^y#Yj1EpK!%EZnn4nwVgB&F z0PqP!m_MR1T!H@_#gX6M}HS*7nyFFF-B*EVS7^N##;<^ZAZ3wC+pm_9&tmJdZ{!K z0<}l%%q5pmXYQQ4>mle$L!V);$Hx7uWx6KHMsu5liiH5BaSnGYJy;lBtfAVfrj5k z*ys$PQEe14(LYAAk(3Z~YRU%jC!!7+;Fv<7z_1M)A_3f@0TOFC3`&4iBFMc)Cqn`^ z&H$DjqT_%~n(@9wK`Fee(9N~|=aGbHcHi_nutwb62QCF+72z;7+;a+WpK)LDHzF43 z2pH$UAqn1L@TmlUNj$V~BYJDh(Xb0C&2KdR_+$T;AcVBb7-v8lCxHo@n~!;9B5%%_ zBRy`r7j{Ly>*$bw6|Tbe_#lvx9ENlZ93oSGh-rydH&Ch-2;Evf#v1(Zq`-|MVB19z zodCsPy3Ii1v+SH7WB3jE*PDS1QN|!IE|WBlMP52Vd*6*A)!tRj5B_o5>mdt zs=jKaM)9Cc6ycK|PY%GZH2h6iw(a1Z5>ZlDO*Wk-W3A^W!@=y`vUnkcn7C!t&V08) zGBrC}uAV6&uf7@&f0)$uzHG!xGxKw#gBu+2R&?qV_PW<*b>Tx9Ha&5TRN7Vo9ncb^ z@avl&j}{AcI2io?dl`K;SPA{5i8h%uQuY=!^fT@bLfh6u+6bmQNf$yjqqGG&nl{;s zEdS&3_(k;hdhu?sSj>?eXzyoR#D9;B-yNHG3OAG90I)6(BKM*M%Ow6xSl9dz^OBk z%tGqsd(t`*9JvTm1CP*2!2P4Zol{^Xd@Ucr6^Hc{S<4f1+5@&}k|73Kq`Rv^z=+v8 z3kV6@bBSNK(Y}rF85)0~jAMb0j1Wj|HJu}kUu(97m7(}`iljihtd&o;?yhgQee3Fk zmv*b8xb1q|XTUqt5#cbPL&(X{$PTx%HHN!}5-M1MH2NH<r&{-t@(oEQaEBtXj% zC~PHcj$POkh)H+2DhEs-ItRdAgbl~?zWZ(y>JO2FjT7Q09O@1g&%3HrMCNT%&vR@1 zT|aHBr`PlS zNUD@jWstuhsBK>NwSkFr19n z0EhQIPc{)?GrKs!GcBdmY*ETD+jM{S{P=(UHBJ6jwes&-T`dMmDy)SxytDV*WWeAu zsR8p=S0G$L00{I5`y@H_Nk06RY1xkkc(HW>gm z{;83*(}I$sLqj^UfO7zPW4{Ui-+PoBf9oA5qJLjvE(qObrkA>%sb%x-NXmxqj@C_b z%aE%_lcUS=@$Y6gRq;&9dVq)%!;)x_vRGVZwRlGq*2(%O>BDC){$aS9{|H+Z+G&?Cj!8*)&G?dfUU`vhHRmrQ`2!re9ZCa`Br%`j@z@ zKhv!|G2stKr4ZC9?Z0TZ9zx5o^+C0dDn>*UqBw#|3vx4`lUB+Gmsn2UeN8~0y*&Px z|F~(>m-9lsG@^t|yFNm@K+(GlV?X+4L*`IX(#v!!Y@{B&o6)ax{>5tiJin0hZ{IAY zX9rayUf4rJHkyeX-#0OPi!6lY^NacPxT=aF;jeT)`Qm18^yTZ8A0)GyzbIR>52#c| zgb6`y=^$RX9a4-gFS~R2%!IEczYj&Z^>HxKt|7yi`E@0PM)OD+H>E^izJW86uP9-vY8jshBM^BoD? zY{R|h9RPeC`Q?d09W3l`*U8sX0KEgylLcMcz_&sUf=?d-hvYwuu5B8?r38=z;8F#c z7QpBk9h;_YjXEEyW40zu&`SodSGe@O2-Yvx76;H=bG#=o^Dj**Bc&NcIyO2@N@!?g z3k6-mQZJyAr~s&dyD-|9^xDjsGvN3jsu*;gfvh-Sc^sDh;qm(F22YSTeMnu2P6~SnJt11jlTe>IR9bYIQWxf0oOe;dFZQwUh#jZ=KN&D|hVcC}PXuV078Y z0ssE2{(Ys%gv6L`k~)p!WtzmxYIZYjRyU_o$zJ)C5}12sbmLzC1 z@|a6(OVm)Ufa$u-=xRaOp=N5Y;Ccj{4>jr+)QSwWMA|$00FH^b)r#+k{I@OAp#cv$ zhxbQomL2cfE}2~3?@Meiw+=35DjW;s!SQwj6#JGI0NGV!~r^hsRI+8Z#n?2 zFt5=8KttUdaWvrEIRIkK0Wjb54gfj~t}4Jf1s?2)=;;Nsf@t?s-`s#!iNknF)JULtbYPTGH8SV z-BKuZ3rn_yQm+gGu@xZdnE!ZC9H1P6DpF829+NOmsy$YVH1s_YEbRYF^vKo1N_D46 zyvTTU#pD^3)lJ@_x~Y9q)Yk85MHx%PaQtj`(+rLm%kq>YG0=#CtObcFEDy)@?b*Eg zqL99A@Go#c|6t?E0r+)?PsSs|j#u-=(YrKRz-W8{t1@CuJ>2I}*`~nSvKp`F@z)0$ zWdFLAa`eM|^*`F-;G5r%^8eReZ2jitB(!~1-I&R7!H7){0PfQ zG8%DOHnI1{>qd-Uzkc<992`_XJU{r|U(eOvm&=Bq8iE|k>DvCJYnHELss}OfW_+}^ zFTwj7Y34?JowRo18N{(% zmCuXv+fz-xCoCFVjmDpUGaP*_D>yj0E=ejSRtq3E0J#yC{cfjRn=#&Y)a%2~f1ac0 z`4pSolD9-&cVM0Umfdn#KIpEqZ6STOveM))?`#)w`R!ymV3+>tCu zAm;WpItIN#_t81sAn)NiE%a~s2hc_LHbr2a56?^Rw+p<99#)OWV|$ z{j!Pncfwa|-R!QiLF_FM zAsmdyr^R$sL%m1TOhJ4?bAI%OB?m(BJS!F3Z(B%AdcZgYhRtq^pF7RihYoLCwoK5E zO>l1dAYI>k+l0t~zt|@LQv-%8;7aa-AMAf)3_r6~0h-!w-#P%o z8GY|4(BM6=8~`-<&gn2wzAp>(N`t;^&z`t)DfX1}5Vo;QgNdv5(*!LWc>xs`K*Bpq*jOIYwG|(ynD=1(IGiac6dKwn~ zU%#}@$-s(>CIY!kKpSnG7Bn&%Q6$I7O0N_4w#o?IwX3%jFm5dnAXiRf+mu+MrWqw- zrJGAx6>kQ0U5z^8bGS$ zjizaj-$*q~PY?ebCGl&xXeM09j521^VQWwM-OWop0~hncwUqp3HXpQ2ull#V;pqQ1MPwLcBBvB92s-sX z3dE@kB3A%&>^5(2odM$jIFTU}-MimdQs@o6bpU#U-Whpk=+}(?EeD_%1!BQ(-$S57 z1e{`^&%lI<7@G!w^;CdI1Oi#$P61E_4uEJOF>RC?EQ#MJA%

#JVpPTS$51yST`Ds%^esELL&oK=Psh9NQfLcbvBz0N(?k za{zjoU?T-AT8PjA&=eL$33ZFTG3<6MNVD$V`g7VjBOyo715@S}a3zR}gKko&%LuAc zL#t|#yao}W@*i?=P2H^}4F(E+W|i3L8K3Y~26~<8L?gVIm`3GPJaz zGhkCozMvD&&>)qooUGZsdPc}WIT`K0PNHc=cs8vsUS*=KQe*|g2OC`^Z>z2?v5YlB9W~1Z04!h_ zU%VT@Rh9BtE$C61=+Q#Z#O7=?k0sCqbE5s>=({^A*m}NucI(y)x9)Ar+_#dz8E{nu zW@bf;h@wJ>YM}Gwa_u%fU~~D%7w`@N8srWMFnN3Tw~O?#x&Ytv2t$|a@A?GvY<`ax z_@+Sy`VYDY8rZ+9(f1i3FUcEFD7;>8*itxj0ARZVfaju>0ywsf))b1eHVA;ZVn5Z~ z7^P_p;v-NEhjOJr7Ojc+b-n~m3X@85l`%WkfwvC>Sfv0(Y&tqxJ%FEX4Ii0kvcsZ| zM72oZBXW@Q>y*q&QCx~^Wo&>;?)KHA5|@Ko*)MV5cy+R=E#uP z*pyVaEAL4B#3TLZ9#0Oy$2SbD?G4?kYL4DrTobu3UgcT7H~?}soz9Ny1`B$(mj?QQ zOf&yl3Nd;!o&Rey$Y1^TVEk`nI{yQ!>SG;6K%xkRdkhj_cc9x9htX?-d2Yo>8zsqH zHaZqU=AFQM5JiWm1g0Zq^TqS3`CE~W=8Mtr>o+Vp$`_^l${4oT>iA<5;QJ`Lp>xpX zOY8>So1IX(ZA^!XFA*xpsbtZz&nk8JY&062KhKZ;b9(llr_tK{d+Y1?tpi{MemV!hm9+Tof%v|U_In2)jJ(6zl>V8sr>J)bJWDk!9i6Ns#OcB0MKy-WVix|N-$NfDzusY zS#VJ}whFEi&}9VBAYm4{CwKt&%5vVZ&$qw0R)MhTbOwJ@N|b0dgIYk6v+47R{;%o9 z#qrh6^b09v?3#0RdH!&%h6}-?R>(m-ucEO~sB4WV39}jk8g!gREu%HLzJC2#wYbJ+ zVSY|}ivD+uCkNo;9o{JMRaGCW>lsYOYEB7Nt|aZjgA#(BdYBY)cvV)%gz@S{nqKZR zCL^g1h!C+U#<96l3zuz%;~4dzAHm2{03a8^a}Boci{k`Pp>!gPGFIvhBtobtFPr`J z^xF#ZNvZUK(K*2$b{IlBDB>>j^WZ1hwyVdufl5VEiJk0KB3P0x*;vV3|8POtxoF!} z)(C9~mTn6kL;dNK2HI^4?BL0Wl~6H24M-`Qa8p(AJ}<53-qmW{uHMhxwv?e>R=hj$Lb zAD`F9bbyEWJrZEh0q-c_k1_T>72v+_;C?(MD0bn?|2!1m|L*%@81{U3*4zPf8UgDL zKwk}D-}5N~p|D@3Fs+%FvNlyRj~YA9eYc;2fh-Bgss(5jkfZ{V6acX!w|D+_U~m!o z#}{b)7dhw(t8uqISr6Qq9pkpkzI_|k$p;XPU|MNC9K`R2(dv3NeLGsrul9wo-A`S~ z-#YYfiGMVTSh0U_`dZLQJfDkGr3vE^!A7ODt)*FXe2~12v}&&3&0aROu$2T)v_w9dx1mLlhRl8qSZ}x`G^6c3Q_*=nWE}I4HOU>X=fEtXf`9JKQr%sqNS zW4Hr9ZCGx_)^KZ8iPNGwe+J}2M{&9s4-Vf=4nMmf$tf)4@O0kBajiKvL9q^i(XcfK z63n~XLdXpQ*8MxVo>$rW#qBG)9o_mwwoOl%SGaB@dwW)%Ywpmu_O+zQz8jz~+p}eS zHs|jne@rQ?I{;m3fCQ1hNe4IrT@d+u*uU!*c-QfG)kN$6iI>43)3XL`RD5wLl zgbG@QWwIzA+|{WB#J_bez%JO%aur-=2}OHW3{WVC5CJqUfutn>Z;YffIsiS}zkgQE zvw}7M^Bh!z3|X&Fut_c1cJJJO`x`BeTm3|wRI}TU_2be+x2OQAR>%ACx6R@ree?Fm z{|mc#dZ>O2r>Q$(SvI~IOuo3dTE(B$Z9BoX=a@$DNDS3d3|V!_)KXlC<};h1x*H*V z8cz@20cnKHh{P!Jy$uwu3!#2?y;?n&vb{N>!EEnR%_DMjz@4BX?zeXcJP2>1b<5Zn z&X`-Y0QqgK@9GzH`0H!`Z3~?6JIMOC4nS!514p21j~%*}8}6o0Z}_r6r(;!6HghGOVWnnB_Uq2vmUF!Z<=g0AmmG9yD=$ba0FnlPqlgbe5v~c9+j*N! zv(6V05$GZYz2ZjL$)Exomu6SQzV4`Oza4s&z-_lf?`q&PBm{PW(vaur*)T3|RyW@q zEazwYsuwp~TTvMKGfLW{ql0gwzAY0NVt`kGs;P zTPSiH-@|R7wJ-^Kgl+kpS5GP;ECX=Hxz5KQmzv+os$KMtvWv3hplV`TCPUJKPFrit zEmUS#)%8fy?lTHdBmkKE_q>u@>XG-Q1f0(t?2`R`&pmQ0@Yhr;t*DY>n3C&Q-Zby7 zUjGS6x{lqyQ|t#*mr0h*4^F1*y#Z~nMTC8NiB+e#Hql~CXp0VjTk<;bP&0)2i zO|ZhDEU=RaL!@(J&sp|DzRh|N82O|B=_l<#_z{f646mc-cnJ>qe(&CMhGfqu?#1 z^!60gIRJh3(0V%I9@+`n0hFi{YbVB5(&2fL{xKq7Hu1q;F;*9^s`(H3^>p^f&KV)p zv{U>rC4+0{W2dpKn(DB*`EdaB92n0?lI@jQdi?!(@M4|}kJ3iPr#GcOt!u>qdo*JR zYA|wR2o1BspRB$u(|ytYV;z8pEPuC=@y>uDOKgh1y3T!uOAwGh{b%?Rb@IEOlb-bF zjDK(FgHk_pHmr2crpl}Z&)s|74t47KmUC&|va-46KLpCq0YFfO)PUZY5vTHO!(E5J z3vD+~C*~JWyoZ5mFoY%m*ikWWfedN@w0{QvclVBQNNg$vD@h5;b1YNPW$cjnb*Jn8 z>*7A&O85&Lzinv*uklZ?%-@oS86nFw(UV5c4qlFMv#w74qr z7t4wsLd>l*sBH0rNwjDQF==NRTfUn&i;)n0tMs4H1LTt#PY%GRH@vMhC4yS1ysGN_ zol>NZqq?9(9_Q)nfm9XKiW*H)H;(wW&kC$O$oZWrg3#SWAc(I zbxeT_RU}3;qBBL}SW+5sNqOFAnki&Oqf-EWue*%s8UPU-&%+GD&o$Q0WME*tEp`j7 zLDSy0I&`4R8g-zRBmH$8xq$BH34t5OWG}!v0ua92McOX!Z1EgaNn~bUS>^Cy&`h447 zEtNSEMp>1yb{@&gs$9G|sp{1sSvSzPsTgL8ATrDM7d(4bUoPS6TFNYnnZZ3w^B|C# zQ7#ZvBx<3`>6@Ww(eqjBm;E&E8BY$tCpy9sLHtv;?fz9OCQHs&?D^@R2ZWrVClB!${#?!NG_}k@Nk6x@Ac!>qR8cX4@ z1JJGPav#*1J_TzqW%znyns*bWMM1DpJbE`q{#LIVbzHRBK@|PAn4BJc%c@t!{N{&~ zvY5S8T02&;>7~0RQTo$Hk1lMVTpIzSMlOE4DwmpEd`}3C+BDri-`jhBmJMIjEIk;k zBs`l|>{%%^gSNHDY8pVfLk?@;ZYA(Plmnn2iFNyBwk)@`x}hT#m|wlQ-I0n2P@&t) z?MPFsePoTied*T`8dcjz!T#yW>~^hs+xuPXXm?XOXGXQul<(1onW&y+|37>Gp-1l(b z+}^`~D4Ni(D9@b)O)w~tNknhC-btCiKGCZ3O?kSZyj$wm18*?F&ICbJICg>>okU6l z7y>LT3h)lkAZ)b5LbxUo_|`PrXuZ4rOets})OQ}8y#Hs(WB^Pu0Mme)mNZ(=zRmX+ zB@@+xlh-FAW`VJDQ_C7A+Kv9)o?Q=ia)=60 z<7tDnX~6UM0Vk{iYIcF$R;qdvHBG(%T+L%1 zde>moQnmUZD0h zn!D<+Co}$?)#rg!B8HIxwhPqN-PiALT;py0Y1jOnq-$qT_yj6!Aj-3_7LEN9;SBLG zZfCQX{~5?{H+b_8ipp)IjpEK(56JuxH=B;2{vCI8NO&WxtUFOk6MI;FQ4f3{?HY&_sHRk ziXmHs$O8D!^G4j2V?KQC_bk3=$sqwY{MlEjjt@+b)qy~p5a)#~z6+XiJ5$>Fy#Kw% z2=~Po0~10_nw_n~?5lcx)4q_Zp3x`(Hc3buLL)M7bz@EpL3Jyux6H_5{Mdie$lSlW ze6;&E$-kZ)b7m7pHxcLhgmd*7r`tdT`y0ym_xt>_{eJ(l|JbK^;6|l_Pzi{OAWC;M zx~N$Eg=FbVUP$s;)tU@8H3Id&z7f!n@g2OGA-6lQ6k+H=_(54&AC&N|(BZJuhh#Z* z8)oa8=^c*HF6ZWIzu&$(-51SoN8cQ}8-Jqy0uRbP1K@#2*9nSB#)FFRz&IQ*L?TMX zO!9D^^Ke;EmKjCDNFlMTrCT+P4L!Y`FDo1rnD=CTzlP)K=?5|P{oWnoQ^S8&$o;X; zk573I;&~*aJO1tzeZ*KFGEzqG8PcxjBT9i|$l z9qM3Oj`Zs}a&I!LfD1taa=TaMkrv({p7OnUxj2Z&w|wt(dpFuIAUxe3s^mF1fx-(8(*t>eB)**km1 zKo|yZan8z%ix+>k^d;TZ@ykZLnL|cU#ypFlwT>8VprA+2(&$^PE=_gwpQlPSv9Tx` zUFT!~JX59!z$6D5l)vZmcMfXymZdhR(YnxLcAX;R*1O%JuABKMK6Tdt$M+GyP8*x* zB2Rf!y_viEl5vS5yxM9M80~m$9Tr_N;{a~!4{`rmK1w|0xX-D=j;(!)LTa*xUn8PFl@~F-bI*JYqUgrQMJ-j&n`yqK|?)^#m zx%w)DMjrlwe8f^1>@7tQYJpn-JY9oO+=2fbp#K4II5hn6eU1A{g+d^^1J=L({o_c; zNbsW`V4nu+N*z6Q(1DYqhrM(FK4{I1v_n=B3Q$F4Fww-}mT_llqH1H*wP?!COlqAC zhkbD72IbF4702^ik$u^0_Th`NY)+BjNCXV17U&Qyv<8v|8bvtY8?zRzY)<=XnvV^* z{HkS&08H|J!N^iVTLRo0)^TesG$Z=aA2gssA(SY_Bb#OC--q+x->l8@x0NEF51SvR zQ1=AddZ_yDA^~@~g3jamVc6{M7;yR2B89Ee6t#6q_qQ{0vnhAqEv|2G{vFz@A3op3 z5W1b|pInRV=aFCyJPf{~91}uWpu>2+%d+#EC_OC^pW#L`UMfCo9ep7cnk&`yj2}uS z#|MNIYP;-z3O>H%B%P5YAdeUTzUNDSUT7*LZ!|$KeUW90&+7GFNkC zu4V`|knMp-q7M-~W_Lk^XSkT|cs+Oy*sm=1Q6X5e>lmyT2ak($k2#r#)c{97TP^=Wl9zHTmtRWF6Yu%=oDRcyVcNCE znAxh$==E#eUh<->KA&>_A1_k`V3J4WfE{*-7-N9r?5C0*Ac&YTmKjqbV!vA;)2bl; zs$k;cTf`FDY7cNr2d~t7!@W!VNDsXamad8bwjO{-IHD~HkQ0HSB4~qoQ_>S-bxab; zza_RX#>l8`+qtqRzB7{UP*Zn3Uw4?@1o+b>?>u>!%eX}d$bUdQsm~Dlhjf8K1p44q z0m4xC-$4EV?gt?MhYt?fnuEdNRX>#Jd4=u)Hnisf@4rr&<($+Q+r}CLa6-)kY8Y}> z8-WxWH;h>cBSe028-(=jbch|c`Ti4m$UNNEmKSm}4V3J4WpV&kDp4SWycrEosy?%YM z(Cyz(&-FJ>T!=M_oYW+C4tQZP ztF!rkW^}V{>-B28c}sMehYrwOJ!I?gocX7gZqs~*>cNe2%VwJ|(H2@3cS10>X*ye{ zvy;m(y;xH*)6P**8y-kZ1tKDBH8IW*s+|>GXZJqr3{?glC+{8c(nt~A4e`-Gj<4m3 z*B%1-JtvCr-17`?J@($w`Mu%ec^OA9_e4bRS$=o6VTb;Ax#$dm0NQSK20)khzcT>v z;N{l=LfZ(Tf+K7l_I3Y#AA`Fg-oJ*fyJ1f$Jpc-PKEKQPgp^gGusK0G*RH9lHEkMD z6>_55CdWm-UY6zdW!qM##u&D}n~4uz<53iEqvUK;sM)eM_=Hg3Arz*h>v}bWDFFOu z6^xGK(g3&w-qYa`l#o@|? z^REhxPl8>GKGT-OUD&eU6b5p5U*tD(%N`v50CTzn687+_;~c5?YM~0C16VNxSb>n5 zw*srinmTgrjTLTNCA2E4;&!&(?!L4LjrhrZXtQTQJ>bWGY#Egm4sPMYk3J`;;ezLk zM>V0`0wud~ltBalQ;rEu91G_%3eyG=p=#+2ADL-I2h9q+z%{h$qj@@Ty>(CwGP>pu?!TXndccKq9CXPRIl&&~5z3FZrEBs*cTbXk8p28dX?n*!^NKlysWF1qNtG3d{dIBRtoT1jL0K)lN zS3=2(8m%-&)^JJ;19-oYNXa%*TFs4p%+nuoJiQszm*)4T4)o}TJEuYs+y?1IVS@`^ zwc^A$l28V{9ETW3Pbvf*gAgtPDz^#SmZrT*x4Z3y)<%q1$iriqXX`LvM4g>y%sjN>qXGKwZRu{>t?Y;kv5ofi)jco{auLjxg&qh9y0c7{buSg zQm{#pMw8DP1f|F^;?ZoAhW0iNn!Tle$F%udPIMLe?5b4|ju$^-rNHBZf7hEG`VkyL zCh&{0E>w*Q5wxIvKscqjNRlc*%ZkvTv6a%82ov)arY>@-C|TRnEvF#|`h zZ|&m&34Ki>@cAB~g`Q^Dqs)h)_4O%3x#B^eA5{Vzl1IQbn*n9s)f{+Znnj{oB_kH%u`tCtJ zIMEvyFvnmpIAa+#Rp@XTQIm6$OQmY;jG_cPW{t8{T?SQMrb=61TMsfO|GtyXu0CL0 z`~?qI(P$11X|HZ>2u3Ohf_0Xj-_-0hu4H)X5Q#hIe{}S&t28#+kuXBlGBj^Dw{Jf$ z^3CV{C*T?%ViTO?nKDHHCi%q*!ANt)Dkc&c#3%b8`|?Kdi*T>Rze$Zl1fGrO#`kUW z?s_2HXT%#x=l>7ybn4}Od&{sZ-+5dRt7L%Vc2*%>SYkIt#;Y4)wzAlNf1c<0OYO^+ zhSPewSHEF*Zqm=2$`n9blpskd)6g~2LLrw=B?A*HC5{A^inNZ?S&<~OnqaC7qzwo+ zn8(=CD3pfAfZs3(Cl1j7SqoL}1W5HXUuh5vQUu~Q#wb>nWze=&>}(r5-3aV7(CR_mD_Ge;vkz;J7;8CzeUI2epOS+Gc4)cK zV}~A4FD{A2>umnn76qTtT@#$utz8mI36NIa!zp5Cjm5-BgHq96F2l|1&Fy!e7gclK zy+5`3{h=~N04DjRiH`)pt_TR@R_R%*b_mjVhgzO{9!X-`Q;8 z8t>HCPrb>FA_MnM-BaN^s;!H(4;_z04-tbx^LA~Gj#{k}O$bF=IqC?vm`BKxNEpW% zM%)S(wuA-RIZ`QWF%%v!Of6&70ND$uSB*F(&;k)N3SdSJ_Y;c4F_eedt_-BJynInP zFh}-jgFAz-cPEhqUFkdb0NTy7K_9~8{)(Rw6*y4q`pCKW`ZcnMK@sKqr}mv!Ec6sK z43RpKM1~e|O>x>#94o;1A*4I_iGjWY&LHOKUbMI++lJ!C-zw{jG_G!~f!@SOb7WeF zY-=qtm=eOYQPk8Gx3cBN+DIGcX?Y(PKW9T3!9!Yh%oKcRzR1z`?rlZd<(sIKPsbvzCDYWig^aMQfll;*j_Dr-Y~hc+eUl2_;# zvWi#K;tC~_=&YS7u45lzfWA7&A&m`2%9w@WZ_?asip`t3ZrTh{M3qv3Y@0+|74+?J z4;R!oQyyL#cLRMwIxc`=un(C*M-%mqKN_t)^yeOnijMW@t zj2WZ%4T_%5*J_J6G38l`|Irki~lEu3}-QErLP!4IM z?#y5E+t79t-+_pBHDR?5Et-J>QT$#W4!cp9B=#-r4St2=eWUX zH61dMRR*FGk##t9I$4ETwAltIe`DIMtjldywes9O2pa5q&^_*y{gmR%4ZRFqjSu<6 z9@yBIARc{h&`XvosTtBW(+viWcxA2C)?tlWZd@B^Lb-6+&5=`noW(4f~$ zkMr<-XZ<+Gn8g9E?<|H|`7U;jnWH2$iRNx@5Q5H;_w0vziPjMoRRb6fx=it)5(a6t zeS<{Bi)fi_ccJ>cwkC%0kY3ke>@cy`&|Txqn|)AkZs*FX1QSFpL`DpPHXZMH#t*8= zj_>Logz{jv+?A>hAl$oJhdc1-Jxi4!)G$W&T!eeZV?}5fS}bCXLsGKjq}54^tl(1P z49Kbs(i}LqeFDGt_y>TkF)*vng`v0(X}PcVuNGx~vx3OeGZ*J1&y>jknBAX#3^K$;X^KvkCoahnETu(TS)fvu9WveL>A|^#Pyv*jjk5ORthWE*6Aj2|ZH+ND z-~uH{Cbj|kVq;7KQ3a?C8CnA!CsGE@sjY?{S7^rK>rfT?_3bw=tt0Uw`~4*rM1l1J z=@T&ev@sBX`4rpU56L@YKr^o$vqCjy`Kkyf<)h? z6MHy6#?%tZDjsHe6lJ$Tv}!QR(pD4JwC<$U<_xv4KzepciXl1x&Ha21|F~)wI{jg6 zO*SJp!dC6o>+644vIRh5X9i7q|4$~90WitGiro1Tqt}XJ_eInG=@g6CIEc@#v-GQt zG2_lsK79$pRlCxxzh8ujbudc@R8{SaQ8*qG)#7? z4^(vzzCczs*wt0;HAp82Fp54ZZW@Z}{5uaH0N-6ojSQp(?uzjSCGzuvw1P2IM0ul(N`U)P`VF~9%ei@Fcq8<*=nB%I+Q zNs_BLS?m$XkhDB&ESoFGPxg+@am^qu!yG}Ou{gw-As6&c&RA>H!_ne_bpkprf2Q|KLc`F?t;$uWY1RyyQoZsNUIHIj~h0N9Twg4KO4$EPc>HmG=e8>8z=B_g@0 z7xBpso6ogHlz|TR_JcLVSV^{ZBd-fl7j@*XA94bH2hXFB{8RV*cwIc;<=>U-Q9>F* zne-%(m{Ny1<|1lYG;=6Av4|wd8HSBAp*C)&f%n04KlqvsPZA0Bg$UAZeS{7YZzIcf z7zNl<1Ui3nii((-D^*>}p$sO^M1!NP!Ze(&LKR^EsG1L@#gV z+4H`sRddZE%dmC9kLsI)>AAxo=SYrsxA=F8c7X%D`&tNW#$@^~kQjUc*wYV88P z*5B7xUat1#t0p7^^Ja;x^}=b0=8X2QGLs|A+&@3`ALQT~3Us;%26xfy>!D+V=X#p% ziWVsO&oXa&^%K#SF&mdyrB2N=D?8Xr&1aCG{l${=<*5?>*;qq;?+QmpT2|RH$0?#* zH@lXG(C#ih;+onRBbNxT12Wd6@;;Z>IVTJ5e}nw*n{AxWnXhSfOT4v0(0qz6#iYUd zn*dc@10p-10wk5g?I#2lKs+$7%;fDMuc%M1K{lQAX^2;&KWJbxYu^JQMn5DM|7_pF z4kRo~rKq>^T9|?mohp(S@3=jI+xsZBP4gHb^>;zLtsYAd$9OXF)rd%3uldi{f;C_h zV|<{V9baExyXM2iLxar|-Hf}>p_eZN3Z(DurfG?{ZG+eC#JmIx7?7+r*Mg~t>_ z+OMkovir@8LJwF>TC4A2kGy}*Z2p32iyOZ5*NCrVvq=vdO)h^hpM|dh@BA+vAT|n5nL>;rQah0Zh#kV|68-dKCVn74*iz%q@Rt$?Vg1gGI%>Qy%=s z`5m5_VdlNp&7+|Vl?vuDxXH(;y=Af`Yzuk2kQ<>>!##hxup`V?M+w9^(*WY zzMZOLCmR(3^@C>4l!lBEhsL`RZ(+*)2s}1Sikh$eIBj+)U*mFDk5h&5J`OBRyDsa< zrf|)`#4}vJ+k)cGz^Qz6gW=u6R@gcpC(G0NznAoLef--#g5ON;u74LkOSMtd+Q8S0 zGV7^*&82o6y?AQ^u+s-MF0rVsylT+0NI$%adpZ=k5Ji0Ur%1&Pl=oLU!^QYg%>+J& z6HXH$nmc3|yJRqstXK?JI!DwV)c-WJJd{eFI=IJ1D7KcJn+-dg;Kq4Tp)Sb_%Ok2e zR7AwA@7Z#+-Dha|frZ{-B}@Jy8ACG-BH;xu}U zX;-2;HHmLI-l^J4LK&cI7!5;cHsemKDJvL^LneV# z<_ILGU`XkeFmcxus3{@Ki7@|CwL^x8+1E!#I8)6@Wlh#Brywii+v)5+5l$6wvy34m z_NtmUR@*N7ew9(utG&(ZQAeX}JholnR~*BC_Do(T-E5YZjVQ3tdU%7)1MTgyl}970JHg zz;s#`O|WT}A$p^I>Vg*bVYP-(>>j1ZbgZQY>KGVnAkg4YMeFccyt{t*^@sFBSwIdf z9BeA%XsvhX!jDcs_XU3)rwUO8O_sE4yC8Dw8}9bJt&MVVQFyuybEp_m?BAj~uEk=B zwuOjC|A5oqO@8y}Ul`rx(Tx-#FctoY<4HNo>>h}%6p#-V`ZnJwMmxHk@Q-CY_`RBy z9pGY^E%4fOg=;}Jhvr=EKv~8O|RurQF0KvZrANVDsogHpwMa}*G*X5-W>1?2+dLzRA>3#&?L4+ zp356*U-7lfJwokJktG#u$JP1co$vNh%~8tnvHoZg;z;GJCqj)Mv9ma6P373%!em*T ztyyRj!yX_`{ zEX6u5RkOF=wtp&<)+Ui5usrp#$wDizABuBOyp9N{o?LPaF8j&OGlSZm`UhFr zra_E?7tI^Zh?Kj!i{O`Li4ofZ32!8lEu>2Zd8H#t(UJU>cL)5yKyDgS=7eqMlM}6A zMo|c^)+a}Nfg$=3))#6yjl$50s$GbH9gePmX$z7^*7=bntF{*5B3^sa8ABN zvd3QAZLH}&E$@abQ*O(cg&*0%TcG6?5msG{d{gmWx({)c$z*7y1a|>S4^K;BD zZMmP%f94Iv)W8oU~L8$ zLr4XPN6bXl)ex6|+e)uZ+Lo@BwB|BDcyI<2wy4BW?I6ZS}QaF4?(hIgl zmey&Fp1&d7$Fz@Ie(d zs61PgcOOqN)xI4=n#YrRM*e)o9n{V}&9?N4FUzVtVPN)(K{V-q(kmS33?HZB8_M8; zUt&nu^pY|Y>VA?DHq;|6nK8x4CGANOlA@@GIYmK2m5%L?AFGW1()al6UEVu*Lv+#4 z_a|$=MDTuL7t{6W38atS0jzmJbDqD5g^vI?P@L9v4-`iUG-Pv^xYdap7op-5QAiSc zIxZiNB`1lb-l8UzASyps;Et-3xi&wnhPX?w0Dg+CBu&g}P$0H_AEU=UevtECFS;}4 ziT6;p#ON1{=_VI>Z#VV}^~)U2NwiNG{~Xo`9MCi~Uug-H^^fRxG@?qAu8z!2NXtVXHpa zhclEH0;0^dFIlmiu5y$3QyduY%gPQh)aYHv)ylt5V`o27y_>7eqt4pTS&1|BDf)Ag z^Rp*qsx8bR#-(L|Q?Kaqs#6qs4KJiR;SY1f*Xd5z_t^nLHLK&sJ3PI(3kYvt)w@Yo z+h$n(w6!T+ASNu7qXFOt8?%E;_mht5eQ3=Hjcs>oq9`76VJ0ixIk==9ZTzD5GyYAH z%jUZQKci(N2pjvvF2ei@C8F>@&s>txqc5tVbq0Lgi07SCvjdx><@^@tZF4^=oYoo$ zxoLctL-oVN*QLkA7c3a@lu{LOyQtoMv0aP{XN%LA?% zUb`(Zu32ITZr3)e(=*||mU)B!IXO)w(n0U;YQotw#y|Hu*RR?iuiXWBR!`r~{rF~$ zXk`R6vEH!^$!wtuF9?U5eaoIRp%QqH$_on{${F!~oPR6(cX5MV(s8FN;4HrT^eu%q z!D56%=j1?{__bIn&B1ea7vun6QIFu>@ZDk;QjmlPc+ao|ESW|y1^iLX9nZf^L&O7u z93}}CCA^L$h|c!P8s=2b-;zt}=2@_mXO#-ek($uPli3LtR4`6swI+|i{?Z$#f|rOO zWJ_HT&}$QLeHT`GOqBCB@a=xvRIDQvXa83qwjeTHKK7*UJ#J7UXD|liiC}(UjOpj9 z-!rzUMO@K!HN6Cp*kEtC*TeS(Vr+!U8}T4Q7>@V?mp$bcu{QyqqyC)B_I2;nZ_CC8 zGC{{eYSvTVkE2 z{B8~_n8G6qF$QjTO#xNTz>XLrw8!fIR9YUdq+<#H!?HPNT06(2vzk^QKAYT7#A4H1 zo zW6xWATlAG6SlaOg(HLFjeXU8nMfpm4k|u1FVe3A_*&cYO)tNp{SEnE28uSc1SH2D7 zG+h~2h_n`+weziHo&hkOi7W)!H^_(Ee0T+qPEjfIlyQ$Q`yh@q5*g|-s5Y2w+7Zu& zBQq_tl~3m#b#~m>^MSD=yOQkg@gKndf)C{VzhJ{8+A}fSh<9~`xV{xM{t7SuQCx{E zOGu|rgyHT^ADr+REuvN%rJV76W|h=5vE`~Xg!1>jtIBt`29oE_-A;A19%$CS@^RS2 z)+Juow(_W}@9`MoP@q^?^O4Q>I>+AZgUWJl&%jT(>I2s3vD9=UMphXU@ng|gF`T`+ z(3Dcl&A{veOmjK)rGfXXUp8qZF_;|L*EhBX(yr_emeD{0$&?9s9`$c)l+T}vp1L6{ z5n>hFBHh2|P~IXo9>TOonWZxXH??ibReEKHn&*JPeapg@1H}F=y9mXtbil3AkdeB(MZQ5U*aBq)MTl^fwy`Ce zkHI~l0w|swUJ6vM(szrYAAOFo1p-1Wz*xjRlB_YDHntl2Xa_G8qS$Sy~q~}6o zOa@Mu6@0b?NyhX*YabjNG74Qc?i&uS)7HFBHoIP&m=T>_CbZPgRe6lu%3iH#J8RaeU)Hu&ZOfsJuvd z^s8qT3H~Z<0z10`IIkod1Si%(dW1&bs%Wdz<)$@@4B_;-m(Qsx*tW04Os}vrfn@u!sVtFz5H`hYYuk z9-w5SvQ10HtLR2LVr$CZtqU3s9784N4;VqRoi*~e&%&nv?I@yJ2$$@}P?R)YOYsrU zviJ+r%ux>eFDY-63d*E^Ij>ZWlPlFKkH<>v=tHt?i2qQ!VAEW9o%?VE1P1#)-S?~v z7_31JHt)wSfeMFBP+ybxK90i20t2;T)<=1w&yFao(6-0>V^MDyx(`#FI?uJJ%k{$G zIW=*5m`LCm9|+`&!MV*^^Bh&ocX)VMy7FdTtT=0TdX(NNUjB*npR+V|Gi%nP6WUeg z67-gLm#9ucnNPR5wfImeQl}dSqp3@tVYkHGSBI9O9+xo6N)Bhcx`tt@9|PUZBIJW$ z;E^wp2|)DP$3gb}G5R^-r{i6ZYYg9nI4qxlhg<)0t`uCnagPpfVT%r4Tpt)yKNo=| z3BJbK6+!ZCy2jL>(iSz*=jn)zXA6d@k}T8stm?%rh3etS+e=@kC$g^$4Ln>7pPb~k zUw&iZc*Wd~9!7A}~13jQ89%uO9PiyHA@E%Y9G=29xZkvEvNFh+YTZXQX zhb(<36&L6=j@>(=qUwDej?j9)FgF}97u|f^w<7vHA&k4dcWR^3V8T}a<$JZt>$UpA zYJbL&y7uO$Cl5C``=e8#@ALm?E%~S0$@3R;ZTth-# zv)apJnxht&L@uU!~K|J|!dU}zx20Sai zE2*}?1_=W0I#bF&yG}iz1gRm9t6;?2BQDMm(gO64OXh*pAR>xABeOzLyf#qLlg-32=|Ks z2nmHgrdk7KpNa00*AcEcRN5!9()OEdQpeyDxuh0~{(aMe`G4Elngr)$;#Xp534)pt zB~v(rjIop@GwV3S$bd{LpKFAMjCLrYNP5Dmcu{9*q4W4u)4MC-1^Np%j9_P#7;ARUplsiZE{v#2`~p5ilMA_jl-`Km)_OC?a=FZR#AHT zg0xGHc&~+4lZVTM#4dm!e?Q@hRVeBdARIt`uPlko7Rnh8jG_~-R1fNqG^a0pEG4mP zdsnUNY-j7q6aBKHqyL8yIb>zbLTM{+beMYv2njPlLl z4$oPpZ~T#g$a|0F;kw1TlS6H?E;0&Mo(|7W%-!y1KiJB-GOeP{>~zKm?0KfLS>bbnqT7la?x?Od)AOi@zsh239+37aIAg)#n;odPzb+%PAR+fXq z04pQ~md~zOKl}#kv`aoSm-83Kij7O!ysB=lo8!fV7rfQfv2>B+{AuX;k>wR%d4^ut zXWn*43>xKpv4{LhY;0&|_gbUN7j#&>}3O>CH;+MA(j^eN9B#w>4 z071n4Ag9kcez{yTmZf^k;F77pO~eUQ@p6d*IENOS6k;>el|(e0H7*1Pq)Kn8E!3=zEN}iB8$%o&5wdU|Hd4N<63} zzE;0cIW{jV;DUAZ2S7qc!T!A@n2t^iCNmhXLQ6p&h5|f5I<2S%*;qcftS+GII{<_G zg-_7Y{SByf5nk7m(?I8LQXaOokVHtgvEZ-}ORyYwH`JCQ?@~($u$NZB~ z(K8P+Lc{Nh@Hkq_eX zMBHf!m;{^=f}qNPSKV(6rW$0NV^u~10jVY^h}MH6VL0kC2_p8{r z^X7dA;Sl!5S0V|pOp`P5-mhZZJTY}DIXkN*>s&C_fr*GDHuulbHNNLncI zgKHq2;_#0_@$s}4r2XLnn~mI&^7D?xiB&TfLlg=;G;_bdSxWSwC0=>$7gjzX_w1$` zJTi0;{KulEZtC0w-9uWO01rL&z|X4hnKfZXF|3m(30U?Z=#ciLbqu!oBBMM~hICPx zZW(1Rt6~vA62fK)XPK0f%yu&b_DR8Vs1s+1#Q}&ski!;cn~g6h1M@uxtv1h67+pPR zfpIK1j>sBxt1}Fq1;~8orw%kaXD43oqq)HaCYf|;Eq=77s!ojg)tY)T8H5?2p>Vvr z1~>J8b};vm=g=Oz=Q$ z9|TXtd{%$eKEE3LcRlSFLkIsUsvRh#v>b`n6oi77veU|M0boLdV9f~TMRTh30ZP)0 z0D1So2ii9$L}X`yjkQVIusPw#^XaGrF3}xBJCl1M$Ce29e$`;zQt;$ZJNBe#Ic7uC z)A(`6V#W3A1#8pH{6T@`(5%Yr(cSTCBS!@&q|K?XueAS@jFoDpTAtX+#a zjf}>{<+8tr*DX=LEqZch-A@dgQ$EvBwW6nQk{@>OIRX~WmCq{a{`sXC5`0-m-zNGb z*Iey}`h3I4BYXB(Siz>I3fNP@KsOQ#3xE4z>ztaF(V3d+wOveKc{O=48|P=5Nyb;< z)x^?Cn&cIc`L$+~P__f0}#q2nQgnFFhYIBnYej zXGNKA3$>V{iDvU&J>jJgjVDqUyz=Y!y$LarmRP1!{u$bc1O$0Scl5MQMjFv5SSM$kpIL+r7es$Ci0b{XtZDpfl)(UDZaSt& zX(Wx2a?HXpv0ID8fb@^{wfB{?gwiUD{$G)6CXy=oV8`)zu|roEHST0J zsq`|2*^B$~rsm}b4;!{)As(N+wXAms@=RV?ie)PQ#>nu&RU0J-wma`zPpqS@RiX@# z7$pcxKD%^xy5GCAseBL@?9P_I{cslb`F}lrkuv7Lc%XkZ{#W|{)6{?J_wS$ES401^JO8G-^1o=)|Ar#}|Cc6iUDu!H zk^iT675@$1SFr2(;Qy+Z)?BUV?LNUZq21XLVMfm*AaX}NdS5d39!hmTpf-dH6&`s! z6`FrYQ0tN;G>7@{Vf7+@4AF~8)s+P0&&k0%Af=!cX{7IM%%?uO4`i_q0)_!IvF{n6Hq=ZVi!*1acn&7;@qyE}Y?>c20r zeK8V@nHV}tsI`Y^&>QY@rWYOVd-(LpayT$s$EN*C*a$f8uq`$w*+mZcX zcH%%iZX>}MK}-kIZ!Hnd0271x%(dbT{Q^ztLVs3gtJ7F-lDzggkm#&6iLOiJTB5{B zcnS#{Cx@2RX_7<5zV-yA?k5hpn5sPCfN&u8W1*WtUxLrk)Jwk9o%~SA=ZUYetK*!r zR4S_*5$GU&cg=)N`NRg+JAo|IzKP>$=hI@S;eh?AOMZ zU>w1r%~s?LU7g4?eiNPfwqRQK4OG#QN3=0um1s^PQI!bIcsSIOnb9AaZ%k*YUP3{G znX#)Uzs=d}U;%xr(zq;9O_o$W*rku(fjOTH-z2l{57~w;bEq|Z2ozF&*&HScwd?i; zMOT|9S~E$JhiB(!cp)S3iWC#sq!&!+_K=ZcH&L5ncV0St~il@<&6UJe`UC zCsn#O@9kU}OH1aCTv}J`Lkfx$o<#ijVz3fpX<^!9UTjj?QZ!@=#Xn~V^4{9RHCgf% zpxB{r;p6akCmggWN2*HT2^7LcnKRQyio`HCO=b=9yhm@4A62w<55L1|yQ>WrVf3Rw zSl#EcIyIY^a<{712ZNJN3LWj8jM_96>oU$yHk4%0-Y{X0%u^-nF=LLLF%W+P!Hn*l zws9#}l?P|P(Q?TX#RMVEU-smAaskM5G$&iI*wubZXaX6uF`ny-TKx$t+p*EsC&VyxIabT40@~Wqwh7jiy4EOW=Kz|Jd@@ zHL605HvW7=pw0F6yx<+`xw=ljKH8};wh5Den z5!SLM`z1eXnifR;{-zG)6vIYp85_3m0v6PCobYGO&=URNKax@VoM2X~6whEG4r&8*(=fqw=TysFs%X`i;myUR4rh-d9FY$r zp!?j0^~d(^rki_T&dQf(|428mgxiZ$!GHPCtcz0(R%?Lec z;Yru{Ys>p#{RVYMIFQpgm=a7_{?=O0OMkP^!>fIGq2N}s0~i;BLrwgb*WpZ1c}h!% z7poicKnLi};6&5$QlYCN4;BymHd$X$GF-#L9wqEy2Y>1p@DNdRzMvh?0y?Lg&j*Z> zoHJnORicn^GX2o|QdLSWxlqu=JpAC@z~Ks~m?8Ilo^Sfu@?L@$(0w_7JAde0m&V!8 z4Li-ZPAAW=(~6crL9{qvWMr_*|C4^{9dLVv6R$RQ2N;6xZwhevR-PAevImQ`rjBo$ z@us%{#XY+~sG(T<7zDL14-%EuJeYUDHNAAYYn_m&`gXf!ohTfXl|S%`gD|2`f>j7E z$B&Td9b?so5@);{=08==Hk|CwuQD3Lc#Vm+UKbWJD93kpV@mKcz^Jfgw3BuB$!31o zPP;9AtUuddTuJYGAG)TYN;JE(W5FxWJ;hVk($=6uY9f%kaJvuiyk@1W7@3gyFe0qHwF8d{gd zbyw$KIfV_h!Yc3fvASoYZd;WsYR9E%l#MEb+wiYYY_RR%?)r&+qF3;8OZZh>n81D2 zTO2j+v6Zo!Tl4mmEEMAtR47G-Q@D)kTu55;lYa-i;@Ybn=C)I&B!VA{?BVmaNk0C^ zr_78KaIE_8M>m~M_U?L1AE#%>nrux6pMV=15QFGbI0SpSpceI4&4Dm>4>#l^r$oP? zY(}k=!gR3m-0`)S4v}H^f+yPNgTg2T0Q+z2CI zX|mbhE3b-c_+h=>LN6`#Ht0i9)JyXf%KJ3fjgsI_kffX(wfp8#*B=?uf|$ttdZ(99 z=a(1#Ilc`6o#X5ppM*hca0ur4mi1QBum-prVv=EV@Co-Ts;6VKHku5KGp)10+QLa= zRs+=M-LPa^QUeQH^E?Q%0p((&Bm|?I1k~sbP5;#ap{l~Vu-ti%DUh)&-^v1{ohaTn zq|~ww=Z<=p*SF|T@ZxA^J9cCMP2Qz*l=PXLlA5uKkBNI$bi)E`4%kPtDC-Ryk?0Z= z8+1z>`uyX*37V>}o^t_$echZ*h)WY7m_0yYS_?KQETbgK9hek;*F1L1tuK|KA*Y9* zn;U)pr}hL*$1$2{GjK3n`sm)R^(%8LGS2>o&-OTFR@9H;DJmlZA=Q%%qrIQl*M(Q= z_TiUIou_>h?=FVY)oR9S=BMYQNNh#YL-5gBOzp~5pMJw=6aey5iDTO?_|oYQ zdwb2`sK)_zr<0%dleU_y$&e4K3p{mmN7wT0aN<>~9$+p6lce-p`uH|$bS^`>Sq=eV&4j7!sa;jwm0#=pHE)Es>a`WAhXDx`nN+scq?%mw26~5wQCl zdm??*mbcWR_BOe{i8$j&E?CX<^T7N{p;MySIOvT<_-nK|$fs#~(H$WP1`u0_9M_w?UmkZ%~CR^Uh;1_O}5Ud2Xwu47`o z$iyJ|+SF7IVtFuO%#HnJ3Kj2tc=Q ze_2bFm6dmG+X2Y5Mm_9e&tg1)Bij7LUQRB{Ccq@ml*39ZyGP4XzW};T_!BoyV2>$K zLhP@h5zGX@EkAGY25R3nYtL2&9!k~blo8E~j+*zi?*=oY>}btu&8FAewePwpc>aW# zNMV56Q)l|E@*K+FGJ^e&)d;&ub4YVsfAK$?Fu^dS9EUL;&qpsWOTHaGAYwK96rcM( zx%)dA*W5k?z>Hs(e@*%RtR1{HNUCDG-$%_TCe%0uRrLbZ?7)mBn51xLCmoE*m+Y`S zB)q~AOT|!GCi4}MNO~guoP=Yhmh~P7uSY!^7;^qc}kIo`O+1nBOF ze}7iEiX-;TmNXqV5V=p2L0Am6dSUZ6w!IFy|fJDAz0L3Et@3v$Ik1 zX`a!*x(LS2%)LDp)xSfu4Uy}fl(5qn=5xkCwt?%vmMQ3Fq_80DgqsEsZAG1RdI{z? zwJJWqpBF!(?AqAEA6ns%j~i`?ddK$IE*}8%qa?$lf9C}vyH9MYxXU@~Y~iy?`XMHl zjT8zI8>)$Y#;)Sb_i?toTCi$SL`|a(_~W$*BR8x4vvA9pH%RU6ahY_ZMdIX3uGtdd zcP%$uk+~~0o8fBCZ|eNvyqB5{C6pAHv@k&T4Gha)9OL%Uw%t*c=$IM0VrXc%V$B>7 z5Wc(^ROkxozwPU58rqIZ25o4(nbxg8T-j@8|C#h?qMDVbVO%n169x<7@TaXQ5yzj0_TspOFOE|Bv6%Q?=(jvwgr z{?ydDWz#2LM3<5ui$nE~Z$@fGNo2Bpa%r$NjX?pVFxseuP3rr{Cp!m(DQW`5+eBdm z5srhmr89TicQQoMA_E|UYnv!c{BRznA)Zkh#gxyqquWti&33wuuC#Fht@8-rv5<#E z+ZSx8KkLSrQOh~jeptBvvch=x^OW9=g{$Y3OHKmj8Hb5Lb=H>wCwNt%n5p@mD;jo9ZUyrcOnLzX7-j2uU6k z(tPK)!ml#MGu0(u)N%JLO##Jffh8)SIiYRNhzSW+H zc@6zUFVbHSXaKRfLLenud#2w<&AIn&KN5Mh={Tg<%)M49M!;4H2CN5%&HEO_GrWQH zh4OAo3Mcp=&4N%`ae`?OdTOooGIwq(toEG1)1qO0{v7A5At%C{(CXDi&h9VXz=69i zB-%MXt2Bwhmz;NUdj-s^hzoXnSc3_=Hayz?i9q5|V=hD1hKzH!(nKE=WW;fHu;hfi&QPO5N&*^vqH%J_mq1H1EU$~3n-o7Y`_(cx-Qx7o!{*WR!~8qz(amX(sS18jWAya<_6OqtG=JKbE?zHg8J0$S8VdnW|93M;P0?w3uvS#a-13NsH zp9&cgXND69o&BAh$Oe9qDyr&#TdCwD*CoFFq#$2jGeozcN)pXQ91f9B_?gC$4Cjyd zUbhWV6{(GMM#4F)~uv7VBWvr%)qYxt5H3#mjRMiR+}uwr~P z8}YeKInDuw=N#cOJ-V4Rq&;@S6N~&ip!bu!hC>5+olfOT{Cx3*Zr6p10=Vq5&qNhf z6$PIUfD=vGA4j~HY)Yv3DgH+k7*{e}Q&N|-o^NbUI6C(Qy*1nErIgO&g$q(tWUA?o znAgI2A_6{;B2#)a8LT9|tU>1dXkU~_ngMY*3Y|WyW+)J;ih`~Grn=}{bN~V2gA+n; zrv&&+c=Y^{ABHt~(C{$oT<7iP?Cnk^H9_42`7}y`rBBpUyziZ%74*7$b?hygti-X0 zo}R`la7feyCnC>TL3#SiIZ@z!&4Dx!B|veCh-#>`z)D#bY$Dl$4uRLOn&~G!Q$r75kXz(BgHEqgt2TS`bD>q5mK_`^++Kxi9~=v(iS~aF zUQ=2!SgLRsN-rg!OEEBtrZw3Vohu>MQ1W}zyNxL(tv1x-^TCLhDpL@`k5RDv{w&DA zhGrO{BQ93B*^^g90nZvmuT8_?9$BvM(rS6VHB z;*wWvqxWjsFm<(IX8v3KwyUp_NcRM@r`$ZE!pPRGzb0|tUvvZ}St0)h9!lq4T9oYh zVY-#V`=uvOyBYW>8urHM{4FWSKVUU_bo+ek1%_yPRp8mQl_-3gt-}`&45pFG1?x%x zbQJ`y3aE(KrM@y?AVknwy_-%3PXWT^7&!qZvsp`ORyA*&wE4 z>nK4|k@PU*MjiI#VSeQ?;|#N}9nDScPv&jyp{*y!aXI=%t}_}jL%BD~EVoO4=XD6B zCOS}GA`eZffnSt=ol_)|Nqy|B{w)p+4kE=@cq)o+XN!C|p4HMa9K_{z(l&Io)6m)W zs($8B2)9n(9#-uezMpv?H=g}sChbCjh6t85_IuBME-nc~_wQC42aVa@z|sn2fSj+y zPx|Rd-Y|2>EI$um9tH3spv2_vq9A-LPlVT3)UQ7L=D&RhqE?N6FzZ<9>B+hpKS!V4 zjEkA30VpP!i73#U!4DeX9R>ylGImoB9sMh+<-uO=(gqJ~;kobg{zSMOt?%CUKD#n? zF7H#apMY9X3FPvV8u<9wHt6_Y1>vq+5=-Ob<`$Lhn}_^<=Wty09&dL&tU}!8(YaQK zqZ_PdoMFq%_?{L8yux;rIMBvb1eU*fStZiIA7t6zq;CT%I4W&*k~+u{>!`YA$4W{* z&d$!-C|b3AVpj!8b4^=Nl0L4N`PQgkSIzJ4F|rM*-~$}Y7- z)=t94^=zhUz%)PbX8HCxF?uZOje)IcqvrC!&soNbAN7wShZ`KmhjIbswNzy{;o${V z?d?s?3tU4_L;Uv{Kxg7YQiY9H4K>7FUyutUOtx8)M{=*et!!A(d8WI-TjeB-lRSaf z?NJ(DfAU$@FJJ_D!$lblIS`QumX{*7OCX%nRYWGN#tO?;C2~qJfPQ?FkHR`(eE0-B z!Oe(rMl+H^ilF&zEv=K(W^wlWRK9cpt9TO$pCVVp_Q?pdH@WpLOY-k>ZP?|mBW?Wo zNe+_T#b5h|r7+b-J;-vmAa))E`UwX3_E^u zcXNpWn3)@)OLrfkmtPnvLU;E4{5~tX`p}lQB{J6%)kcNovH^Zs{;AU8c?rbr-M-w$ zl0W_H@UXlyg=Z(Lt#RAflPMS?KRIu>*NpK~GpdW!JVS>TDUSLJ ze1E;}h1TIyrSQX;{qu3FA^q~s4J1=VRR@5*&n*IU|NiNA*boxOztZNUrhp7#1?2Y* zCT)9hBA3lVSJV|UZOYd~24oo@24NtV9)EbuS7lO}Dhj<6jbneg1=bwtu zbqtl1tUV%rk^(eECJ;?(b%b(g*|p5u&a8e8{3e`XGiN)%N9aFZ#!2eA`~d^D-l<@g zsEc#?(c|{agLPvftBJXAJ=&vdU^Qnoe0DKl zOCH8P5~5|O9C9}#>dPH4@Qq*4SM)2VvejSKLybYw&hZa6%IRLDIN~(2Y#jwWZ4s{f zhTHD^E$}qn2@OBjVYmin5s-mS8RY@Wh3KM)13stO<->;PPZkHhmhOa)w}pv& zrkd$=gN1@vJ{Q=xuN=B_8|nlB5oTALsdOzNW1%x-PdO8{m$XPtMhz(({6F5&Vz;gw zI=I6RDS&Hjw1Q=i_aR|&IknZCg6xrx>0a-AMoFVEXoZI-nZ`Y2NS1j2Ccn~7((kGh zpAlyyD&vS-KcrdGzMfX4S(^19gO^T7apzB5ihxsl9AJUPCJad5nz%(|AcbS%yuTUKb`%8Uhkf^ zY%Jl0Av;sjj6HEL)w#_N3jGS9@uckIG*4v1?|uo$E*Lo%<+m`59Yc&t3w*uucoBfN zVKKIlP*UcquC-#7Zw*oG23IrzsSu|$Eb(7`p3tCpEs(8MHv=zWSxJ&c{6DpwWmg+* zxP_Bo!Go3HQYgWp#ob$=P~5eY;$GYW#k~~i3lw)R?m>z>6nA%bKRM?=oP3$JGBf!y zv!0pkeeHeY5AK+K-#hjR^~bksie<06VOqQwJUF>&)c^?)_H;c_Vae#oNLKD8wq;D8 z-Ng3tC^JdisZ%n-O*SEf6$jS(NwQ*xp*i0CZOVP5S9u10KRZn*r!>*vYT%Xh5LJ)) z8PEgOfDltAUc=zr+6OQmqx9+6Hx4uZ=%;xPyruJMq%Stf7g@|<_KTzE4yi&T!YBUW zpo(N6PtQQyF7)p+y#Cb!k30BU-BuK_Ze!uwHxlX(t)&V{g z=NjsGZ{)AN{qpZt&gJ3DtuOTS$>NoQp)dP=dlnk4Dc1xng!u87UI~$kh?72laar+6 zv>Ky~f40D~B=3-AS9u1UZI^}q>9m>wFho(}%>I+Da_Ge>BzV}FJ`#Z{B?&$2l zqwf;9w(pMBTz@d0TwRMWYzFw_D-Av<3qAv&TuRU$E2ymT+Yw-8)OjmsFK+0@)y4hk z-9t{To&JsrcA=(pI~r~$8xF{LKqQUWS+D2bn(94sfhhz%D8Br%DYcTOvTgY{*hb_N zhQ@UqEDCk!arM{r&iT&S!4Z+@XzxQZ1gI2`svJ!>KU;Go#e5~ZG@a33N8sVUrG_MMpKnP@i*mQ4ND#E&Fv#t`I;PGdmhk;+NmQfaeRRwPK81AT z6FB1S`dQb^@hPEL4h9VG`(}+cr)V&{8fpuh@c`at;>0`l^zt^TfO0#zPJ0uomS$XA z(WSywkGGlYiMIz_z){wwD^CzOXHD?FkeFJrZ3~c+^||>(XtM5T+0sz5fwO;NYHx2( zL^)m?Ir!7W$oo?nfBvJ+5Q-|ciVPbV)He+BKs&{4C{JCJ^i#;9g#YB?R4BrpQvjUv z{)&ThCpnEsG(KRUv_#igeU&=^xtrc1)yfWw>Ny*!zWV8T--2IW&MC|9&MAp#ioQMY z4n}eBc&1AW&WppjLR{JgzPkJzsT!R7+}3vcXzp>gzRrV_Q*4#0J(KLfo||7Yjq-in zkux1xwRPf{ppj*p(k_U)IwYH7ZVVELaCyczAvnOa2yyW^dDv|eb$i_o`K!m;=ftr( z7wC0j77UtgvWTKNwj`BodPf`rR(E%3`te~z5at>y{b#;Bn)~kTPv%e+Tgh*=DzsHu z;$E$w&Xwe6YO0lL(SLoBT&GcCYDEXdrE9Ziy)t4QvbbH2Z;&C1UpA?)q&DD6=LDk` zrDa_gk2v|=8ymnnYoZV!Q$jj@Xp~l*a|PXb<>AbY_b5K}J=8%{(|{FiMy7G~-5VL9 zTELXCkrXPdj#iOYARDpNh&S zG|vGA0sEd-QPXiw%e}m zNYV$kRA2lp!KaWZ-y({^IC^Mk2fQCg<;Deu5qt*1M}f8dq4(-p3{hd_G{=U9{kEXZ zZYOCE5Nr6ZBwTNs`c+kPN|~irE(o=HPNb#5W1LN^#HL43hD!*$6=5PCG9GdTbmnq= z)I%TBRz1BsI;wXyGz=W)QG9ncXTf>3li{z*o)n4Jd3#XIa>K8kys~IuoH3}ij3Fjw zhtujn`RtzeuZl(S*9svK2)f4kX>??GZtl)qm3T=)=xd8)3}1Y~PA0Yt-uSzhr|~C$ zM^RCSzMJ$pQ=*IBi0r;E)npn7Xr2a&;Hp-gVVi{yz(z-xM3t2>H_{0r7YU0peQ6k! z#4$pqU%^E=0?X)9A7H~6m&TJC-Amhdm*$j8@#qz?*xUtao#ed=tYOGrGM>!km4`IE z(F>R9wQ&qUX5E^UgdPEXTyTP2R-%G8+sMz+S@6!T`WqFHRZRdfNl=@*9GPS3w+FS} zOiwjUP1_m{Oh&o{abtrP%TLOm5Z|kKqnt!j+B;@_t}l4gnaqYtIZ9*~6lfGo1O5G+ zz1|qLG~9|QO|mL5t5oA3Y(cV~0^}~a+zz8zqa4-+LO$;#uQvbFd?E*Q!N)Z!RovY`u-%Key( z#+;qopM3>?0XXJV=q;RfK4hOT2%8ro*FuQ{Zdv1gswlh+>*y=yA*~H54&+FMjGZ!Z zjLUkkG0F@tgz?frazvBSN(x9OoI0A-)>bxf&-t+QN~#+{W&9eIT!2(U@j$!mtpZC~ z``}T?-D4`oA2IrXq;Y6aRz!*#tqR$D3UH1{_EJ}H)WipQ&y2=HGijlvjYU~>E-YDE zonL<|zAYSk@wDBP`%vc(QU%K$p0_;RnqYM{mHWGUdpF|+?)p1*Adrf?BJCXn)zX8!%&3Dqj$patgUsv9VY79?mU zh7)TcC{%v4npO)O1NS;z;^%#d5uJzQ91u@fQIGCW2Kt!NvoouxcmOOIp;Q=7KR|H{v9_L@9KCv?|XnH&UqDQYQCwi&}7;fsZ-j& zG`l*rPCb`?0W3h_F9G>*2oTt`UJUEXKWkiw{Sl!u%{3BW#}t68Y=gGCp&RRTFWEj~ z5tbjN8Kmk@6J49AM&6_330A-OM6N5Gmr(<8)EL>^7rbyyJ6?CBLF8Xh&B0 zXp(fd6|;E6AQ}$vHws3d*d*biF{p3ysK>@l^18b4E6oA&zteut-F;>RG*8zYng3Z? z;!%3IdOc(W#|Do6F4HREQ4Zi43ou9RSkem1jQPocox{a7ZJl|Cl<7Dwo~YR_9tsU^ z(allo%=}Gop~=!bThbC#%S6Yw$jT2KphU0ey#~%e#^+Ao%g&4C1wf;Blo1W9qu)j; zidq~)|JRH&t`;Z5-G4vJJX%kRB+{%>wyoKfKLE9hrzu8+EvDHmqLqsuMn?M(5aLKRDPDC6mwQS&E$5zU1} zoqbBipp`20*8r}o|7=f?D|euMTN$1G;hn-HTk3CF)x|GXP3-5{Ly>8uo^7gXdDD{V zcN;p@=sB3>L?Vhnjo-iiW~wmf{g{SL=xyDJy=)()&Lk$B$M)jF!C4OsO2(rW*no(t zBYQKEu;E44LGPG$A2}6WM_*qwB@4cbEy7|WRMK@HGIUG641_{Nc7MITwvAUA6_S7NS`Sdxc zYRjjs{UhoG`FAiHi)8`XjyYkkK-BuXGP{ZCZok&HT65i&n(3I-bQ};tZ06pCB}B9Z z;3h;6apIk_mGx%P`=ca8MimO;<+M-Hn{xF80rO>}{PLC?cn&;#m=l>VnWOV{1Z} ziD<6#HGVFm^HC}8tTy-*w%?-JQa@dE2UwNg>;t<{XHc2pxBKUWBY#HV3hH3;pfy=Y zh+CVE#)GesgS|bL%9z^yGwowIOIQKJnBLFSRGYBZhgu~+k;(&i-sQJ8gLqppgmQK1 z(utdjvM!%1?wUT}bu24DOsldW?Nb%uHSVv`rgA4kZE7{{U6d&nqr|*Z#!1O`_**d? z*}?>TJs(>Q0tR&j^ZNRFVX1z;R8Gz=z{JNji_eVvb3T5$uVh=7>~V>0>eQq(iDtbr zf=~dy6Ws?)LqIoF@|<0s;b+t6R^a>D*BY4Jrm%Ged4R|jjU+8$pD*!Un~?**j1BKK z@yT(FOB~8X0Qjw+*Zs?-fNI}~(8*ljXEr|;P;Xy(U%J{lJ|9=DX4fuRr1;##2I zjp>xgC%*+4`^|;zb!@jYrqRj?EDxI?=%tNV64^}3ZqZ%8uyEzxEb=m-_s+3W{HBA- zACFwX_nk4@{*<*V-j#a#fuYSUMCC+E7WB?!QxvXHkZ?3%JI#?aD6V5usByGXQVfDD z>|sP%J^D@X4I_+=+J;l}@7(_A)X1lBFq0TND5_8eLF`Q~V=c{{$zVQ+|0Xy~h!JM$%NJ#d`m~xSWN{IR*zV%qIxue%CX)Gq~07 z(?~h8A^(=P1=EVPXll!^)wf#LIAcl)MfGPWAOFdouFdzRVmG@5371Sp(!~(Tnu-s@ zrB^1dx1~YUn5sC3EkG{nz=^{BCiIgzng`#HQ8yDtPj{Gas9L|&zn3&*8Jd9~+^Tj{ zq(Hl%_uPV$FAq>`Kxam~*DkjHlM8m-OEXI}$56~zOYy8@f{YW_$Hy5k<#7FBsC zc{S?whqA;jC(0nFOWAmaI4w|{B^2dh)hTM)c#;RR1qFL6*Yo&`;f||e|D4Z{P}MIWIs>RIV0KNFa8!^8_w(VKm6IZ^ zbF=wy(1}E&LD|qb)o<)g3jP%H?RZ2dmRsqw&kyk5*Pu~yidB+~M zjl+LXOWMBY?KFqv2ky6}Rwi0F_6`nkI-)51-@{+WRzSOo0pIZi_yQSXX2ycIG&vB?u`k3}2D_6EMEoDyNY`6Indau=#DvpPFBEQ1{_o^6=MGSd1bah#FlI zy);Qa-nJ03P*U%Juu1XSDn)WDjgFOCAOe8W!%l)4?Sk%3H}yb;X)Lrlq^+>HxM;wd zszCPxrf!I8O@11(t_9m_obK~g76H&$4tE@f(Y&RtagJBhu)vDtCxb4EaWsTpVO=1& zPdeeR5(L%R#l=Nmo8%i-%X`SpC>P+^&@nmG8B(ke1onF^b2=s99*zmzG@IfZ=9dV; z<*m`wmFmN%R>l`0*WaNw4E$4&mT1@9+}L)v;;P=kfl75*%n*abdd0DGPO(QkQ`on0 zqMnM2=bdWXJt}?oQr#CjU_goS-jDP_xdF|ctbS4LTUQL+8?oQ^CSpk9q(bXBUhckE z2m1xKH#7ZWF1t=Wx__~?w^vS_X#Z>O%aA!(f6wu?og58{cpV=j*^>*~Q8LUJsB?W- zI%eswLU3-o5HVru=#1N`48EFUL$Q1&fP~b6B8Wk@6Ddp`O(+b&^4DtR= zD#+!=C)@VGCttNNXGzUUi$R4ekono2I%b?%e09p*fwMQ&-Dq-V@>vl ze+EN8@-lQXTAeq711khb5ynH?f^Rnc4H3TB<9*#@40b%%?+dE58&1iB3EHB5Y5<)6 z3O zhI~HXl9jutvv<_gbTPSBQ}9#^N2j87@+J@GPOVD(+1Eg#jborqOf-J({)PkYzcXDZ6Mc~g>Wt{T8UTq z*VB&ay%p{rOP~{ep7u_tq&>kg`J+e=(c;Bt04ePPU!tA2){LuKLalCDl7H&(*Buea zyOC~e=B<2|h?6Ybt1znb4({klReEqlx3j98>ZSg#)NB}Hu6(ADt#u})e1`uGPS*=o z=c!1<#>J$1W%$^US9RN7fMS#ypM_M7$|u_>bSXO8tJ|^ZqLbzNA3v~}i>la2l_ulp zIs0t65lGnSq!V`QOWeIvkf2d)H58rDz93c_&e1t-u_%#JoXsU?hL;zK@z?$3qP6j) zEgs;!B@y(cK6MzdtR{Gsr?vlh`1naaB`x3TI#269aeQWlU+Kx={my=Q#R&jQM#E&) zChsHh`!@>lnh+UX_4*AjU&`+ASooAHNwPVfZGc$rL)!cmM%+C-F#st*n#0#od$Zn; zJk0ogJTp7KecMz?^M+D=y(9Fs=ZV-vEK$QVg#wsI6x5XsOj6+5r|w&$$(BA%b>(Ar zUfr|w_(4i(nTAxW^f&@Xa|8yoN)l~S=0R7d@8KKABU7;?6MXHfnAiF#=2!iDa~Tsn zPjADr@4{D&ck6=DKcc|{#R2|fM^8|So?9j2UVx8pQSJIX)H3opy7s#6)7{;xBd}K_ z1oZe;zerPt%tZvOlz~p0U(fCjfPMqms6(j}inTNw>VoV4zBQ;rUTxCiHd4t=vv;s=_GrzI;&4Z zg+MX;w#=GoWI8GOosHxlx?-L^*-mpIw(M_(?4I63BUAu@M-SPxZrTIK z2}AMn&E?6!1qBVG3e9-N@iaUSNN~c19p-_3lmDhI@hI6*U*T>vA=Jg8t%B6x9Jb9g zrl!G+wn@8VvX-0BS7lFMkqw4lRSCc+n<8se2JM{EO8PTFfYrt?IznF1Kt;w zajR6JmoXW9?8g^f=w^)tX-Yaer6}2el9NHyFgL^ahS6Jb_Yl>sVecs zk{h-jhR5qbF6EId|Ga++yqbwpBog>~qNr!$j_I#=iHmFOqifqA2mr<+dBpRC{-rlJ zbe&Pt1PM}ZA7dE;zp2IE-Kj8vCUjNY(7}GE2DFA~5(jrb#02>2La(l*D85Hj0lr`O zyKuCjT(aW}n7rrcguQar#6Qfs#V9qu{S%b5>W6Q=pe>h*Xo|$f^VJd9FlLl*B9&SM z#o7N$ylm7Vr)2trnVlJIK|(n$puByEwqRX`B~s&Z2>(2$N2xlZF2{Vz0qF_2VG~?S zlkUp?gLeldb5;oTbQ=L4oiuV_F{EyLM~(4A@;0P=n1cXPuu4~7vis3|90-m^V24^& zVNr<-Bcsj}q?4vo8)IO4<3!AL)!w)>?mOkHM_Q21UBuEeqIm8xMK#Hh!!c?2{4FPB z7O;UYovM?^EAkPH0sI`5PYLzm+RlG3)XE?9&zt_Vl@<*B+Y}8{^BTSJ%cKf1&&fk3 z2|xngXOoB+0UPbN84-ogrU?T=)OO^MFKHlXrG@l2NlxvRa*! z@{G9^#NWvwtoHRlgR{H3w{#C z-R)JnF4C6yt|zb0e(7;pG}~RaR@1X{e$7wxN^g&yC{!ZU(~j?>uTak9OA zJcs%olx%+H#zAV*ZX9F^c0C`HkbeiP4m4t$5`oukJB{Kmtzc9rxdO|!XEpN(Drp!t`cc-c?%pR2}3>7%(U2-PyW9Rq?&4sQPL<3Dq>L6Nc(< z%_uVl`FIUshiYPp*0w#}-4n&koIi>7&Hp^I9}MIw-VkWZ3Nnz`A}HM|F3);B1N`VM zQ|+@m&P|Q-QHGG|0v45t8u(g4m9jVEyV+!3E1p?MCH8fmDwQO=>VIdJ5=;-RapE=I2L34KwyX@NhnOu1(A=YqN zzHqrnZD__W;vnfMDgFDwhcwP8oO<-)J8Ud|Iz?xZYudPaZ##^9B>jB8Cl73Afo&^CJp;qcKo9gXQP@i!vl5JmN2*NNkGQeC1Luyb*+CPBLW&me z2*S8XrCLJXO49v3X@Ibr4VSYF^4l$-<|qUs>A0?YCIvo<91TTg(SyMbu5T=q^S@#} zUQwsyaI21m%1}rG1X?ZgQ0Hga<-kyWPpHhwlsRq@gF{q|7m)Hq|Cy!*W4yM;CLdK* zm~G*e(0^4Hev+VSD`S#|leIekrPig_@{kQ-? zNqepO{(-_;59P1h81hgb+dh}Hn=4Loi&y5lkUVXMfb|Cn9}*_{+6BA%xKABW{DH3e zINQ{o4PxkqE@dx^{G|Wx(e+;;jNixigV1{KL++cUM%JF$1D;_JT_7j3*~e{f%QxTV zleutb22%2sQj1^7r%6zIlJ#&${PcKjj!pdvqI63k(t{dhDcjpPu;F;{v}ue`K}R{D zqwKb9qpt1g{b>C(n)P{N6!U1mJs^;b&&51yWl*$oOb=ykTI!81I|y%$x8eofjuI7O z>Vr=P=n`DJt-h0bPjGo(`GN1Ksnoh5w3j(&gL}nL55rFchg`~?%5w0A{FMLtkD2JV z+htH7m0#Zr=*e-;t$_MI5Cxh5HPc!+5^^1Ro|)^u%a~wzs)t3qz+?cb7^my|Pi}() z*%0ZVolq&MUY?qpWBvM$%L_l9*Q?e*n$m&@zx&GN2ET9YS38~~tD&Rcb`GNTRybzq z)TXxD*ak&Ah`q#q@ucpUYX+){<801ST!wf|@?2E0O)@Cakz+7g=hfPi%B@kFwOFT2 zCu$beXknbL>AFAO!c;@oM{EAW;?mMw+Rgwl-Pt5Uv^zYgS1`oOHDstr~9 zjHEzXK2sQB>@*A-d&6^MpM@@+wthTa;$Db8ij>Mi6k6Vk9lqr152m|p-tBbUvczoA zN99xi9NBE{mWHo*mrvXZR3-*tSEKhGL@4z9nSbk&un5YLq`Jdwgl{mnVEAN1gd3en zCEzue@;l$z+Sv5!W>2w}MPOg3H%^d&iIjg}arGx#z&AUldm|C)2ERom`?#G?6NWEv z_mw8;e<;4TI@(wENmiL|BDgt)>ry^8`}eD&firedS(=)DNNzGF&>!VZ{nQ@wx0!ij zS%OQ!gV*2OW<0>KC_0%|gR%izK7m>$(>TDUN}g| z!%bhadnKYOD$RwHnw%6c16Yja_DlS#`!03Z7&S{^s;4l!&no={*zT$$K1N7lsane& zw;o)%aCi3W!7T8sF}SV~FoN=mACNB3%nXUm+`+V+aE!fKWz@0xJkl7maobd`U{^*V zB#ws@yQ)+!qzQf|(JCUl`=P+_ZuZrzuJZPf%=Lrh&+Cp8`3%$Y&A zIFF3q4epMmX3`-2Xcm(Mc4@LHvGLH(1ut>%hdjX3vvNTliZ;5`jAVPg)ZZZ}Zu-S} zM~E^0XmUnqoz;9X8-D<}4ID_DI&8stFi&*$NFu-y@}d3g?qQ#jc6JU4itNs!9o11V zPPrjQwSD)NA>Eb_SDFASj!NYi>k<5$ESd);s2p0}A-?Z_*NGZf)0^j_OCCSa(GMIF)0Dyg1U9{$5y*XmK$=)H>bql;1$eRTU~^AOH!9ut5_C z{G~#p3`kXEj-|99hCO74Z}`fu{`(V9=N)j)v$Q%Sj3uCMK@wI6Fd~t3+*XX<_=SZ1 zCAPX6O#K8&ZAPG#SgMx2xWq%E_-L?;khe|UU~L6y*|=VY=)rptwqCa%;9b)TI|yoJ zsNdJ$i9K^zzRwy~bs3T>1_NfqjA6WC9opb78bM*VWF zqrJI1`iR%yyOLs|9rF-(l)x5Ks*&$4>&e-s+WZ( zOjcE-^uHLbz8bao+#xtu+OAT$2lfF)}a+gGD@pwHk{T7MA>w$Qs#s zy-^@nixHc)|M!ZWho@w7)uH>1<;eJU^jC6cetgD5fwUq=3mq(l65KsIze43bdHDCF z5hG|yJ9m4av9^E%|vNw(~QNRfA;H&`L9@GIXyQMm% z#2v^8)y-S~>35(5N3jg)@NM(96{d$Lqg^~y-k^-9hNl)aFK=hBrv8b;SonRCf?WW~ zD9m-9q9k<82eEcrNUG9xJrwz=4&`{Mvuli+{8$=3<85#{KU7?t?ZRkCDJv58SKEnl z&%j5<>ww z6qJN#><%PkFuHw#OWI!3Zc$)i(TM75lE{DNTN>H{d|TZv3@(ZysG3 z{OXnr5`sN30XwS_ZR?lNRMQe9Aq=L_XM0z#d;EjNo74q;+FsD>=di~ivjXegL}x#@ zw*N+VFyF}3l(j7GJQ%8LBFHH4B*t3|$~bZa+UAbj+HncV%6^v12VEHLj-6Ar>~>pg zhD!jH9P~A}T-7yMcb0*hRPWjFLsV1pkIkdH_ z@75JF|KsiSu)j+w4jF)t?!11 zYZvY7<5<$JNYGR7KA#DN|ICLCenTvAmub%aNnBo%Zg6W?D^vay!JyPpgbdy;@v%c+ zU0av|ZiPNei3|)oP`l%bi|umVbS{l?Lrh`r0ZwfjqA~b*2Wb0DgH3Nlz#kx}C3CiJ zs+-N3#l_#N0)m*m7Z5t?sSI8D5Yu@nM##Z!^MK+!;+*< z(@;q*x>spmQ~7AE794;M`Dw`U3PB_Zkygm*i_9*K)GpTKJtdbolyyXJ`4>6&`3#9D zwj*c+xFl#@XTEZY1;o`Z+c1+;~qFWX8<+5>1#r0LuQdbx>y)N!_?FVAejB zE-NphIC+RKpCXf>qBS$Tu#lRHd`@(<0|sv$sX1hXGe*%i9uL@tM2U=C&2w zyI0dd_&%V!wV!N*nVyl83NLxHC*KS;jV~#BobVE`g@R~w_vgiJ3 zIiz;Pz4fl@(v3D~Mp!)7p)pQQ#GLVL-ekSp^*yC|2`ldu2z$CotM4Ag;WNIZ_73|^ zY^nJH&N{T6{2H9h*<%5fi>bXgOr(qb07ed`Q{qMV?i99Sb>*Xcr2`C8-t#SI30 z$<(jC$~yHck=%F@p6<0Ka!|44CTFt`esq~v zMXoqe52QB0l7{xlciH;ei6ogss$Tnlf7iS&d?7&x5~nZe`Fjp9Ix9+9JIz-wmioE? z@;TR{&!njIQIlU18Tunpi(kvE=EiFu3^(blEX(K>Iptx%JmSsgtQ!q}GdR5+tQIUU zIY<7nye6zvobHs1?EvVqB{5xeuh*yjDJ(828UAY4yBEZi{l3dLh;M%mc|&>e>3@+= z3NMB^?B}De=cA_xm}1!c7s;a(-|c+W%b=uv+TFzVf`t1s_xcdALXFdqGyUIb&!%lU za0~qLFoa}K-Ne1@3D~P=NI?EmR&IGY3mT)lD7r0|GIb{u_aslr(BDX#eDZ20sbJyn(iP`h#a|5%8*_J# zy%WLef~5@aIHmm^`vuePk*N6U&)6>T#zR7;{SyF_Bd>IT#du|Tv2z$ce@aK}(lXxO z7+A$2Zgw!7^tLWC8|s|YZ!3mx(xqRcR8(sX6b3$x0o7=O9CT2P?9@ntgDJ$W^@Qh@ zjbmzZ5=N?chT~y+!ZY=Smx6C&If3thG1wYKsY`x{spRBbegLg?Jk(A->C~i6XPsww zUyINC12go~kElN;A0;bn75Wu}oW^Q~-HEG|m8kN`Im%K= z6`8mFUnv;%!K@(W$Q1yTE)0zh@Do*T7dgObFn`|G-k}~DN|SNTO!l_*HC%&=f4b<8yE=>x(>x4LVsCNkRtXb{9S+V0^IP$_Z>E?d_QjIEdxa1buiEM+- z8u>*Wa@s%_Czw?I(?@3+wuW)p19&1=~|FzA`Y zs;Jdf*_ZEEg*b0;ru$ZKqi+<^B@hE1MXotRcJyp;muS(lYX0ti!)5$qB2f`o87PKd zl^FbX!iKyM@`}D@EFWkFGHIOSSV@27i3hy!o(bgsfl5c)z#MSBY5A?|jwna7N_V9# z_@P!vw>5f00K3bPETkhZe${TE7-i>Hi&oCDJToF#1&h5#ck15*{iypvYHC9{D_6Ox zzrYYXG(cewl?wyDjtr_hnEDpgp=NTzICnQVg1FLLRNPb-`0o6gZO(o1#-}e))HonY zL4=Bhg$l`$c_|jvEt*wa^S7d4M_^ziAF}%Wkh&(&>8IqZnsLB{(L(Tq9dd@$ZbL|AS9uWOG#6Kxha;)e7jH{_^jXOZX!!*Sw)tr<4Bgt%SI6MRDiSvcb!gz(SB@ zWp+ZaDb$xhmcg;lc_m;@&=L%yKE5$GQrNAnT(Q$uVD9rrbdBs#Ho*p+pSOoo7j!kB zJoz$&1z=fhxPhk^Tv22uGR^SpDM+KlNTS-FxVCwg`+{A|%1N^C_hk2hO=Jn%;-W$A z$Q&Y97v0>1jObBhRmvoy!z{R*ctEHmdOEo_J~?$bq_m;}5og%o7-tdm{lR9x_1_?p z|EDg|D*P0xpE;2_uU*Tb)0$dwrqX zM{FZ1V~odUUe4%fw}^FM4H3{??YL;z+P%u8rV2cyTCZ_5)_gDcd8sKEhmd9bLfOrruj zIAd{KfZoaD)+nZ?%u{%L&F(Y(RJ8bhq3N=+Mu(F;xZo^dYScp9J=Wixo;#SLr?Hm4?J9zpT(|)3?w0bDigUL6#gCC-sqAQ_1uZFN?r#F9hBIMYE#^^JIj&{7 zjV$M)CO*$k;G3&2x#03Y=&ZRgM5uvy?m3(d2cA`F1jMG#8%mte)+^F4Beq|71xi5K zU=KEW(|oi|LUQ=>+nj7CN|JP%M}D2?Be*iC(>EX(U^!c5x8?upPFYeYr_W`#JKo3A z>L8XJ2w(1j;H@0jNf-d&SSnHYs-t4sj)s~g{0>Gga-OpTAtiNG7atK9o-5tOc1bhZ zl`l!uSH?+w(0xSSehLZ1J_BUVqt=8q+OyeHE$G zFZ210iF;&tR_*gMA?lNT{(XDfhTqN>9 zyAAJqH!DxRX6b_ZCtSuhru$iq@--zYNIgasI-kBXT4_MhOOQ6n7gl6X+cn7biLCqPWbO;WYSoL6~*5PDEOT#xJScX(l5_(SJLSv`B+ zz;B0G0elSK@=Omy<*O+c82#@f;l8}dcl5kLbs$yZ+bDE6KsLB6HLSS?KVsSc4m{J%gx!@^7Uhi*0Q!COH4i)=|ADcvRxuFlSdN+MBE}zFGSm^UfCHaPIPS zg>O%Lo{k*q^I6Q8UW{FtKJ2@aJu2)KlX7;^v$k->^565-*LDvj=KAV31}W5!(6;+E znnVrVcp0XyZtcvydFLd#{PF1_i1MSrhJ#4M+Y zMZXkJXgH}zB38+v%pWrNJBaF7-0Mky(2f80U(!DSz25*9VUHJ$@6dyHHx=(&PD(b(&jVcq_EeIu@_*cm(`}3Oe zp!sKqD|>qFQM0X{`%H%sD{ZWyx~-Y7;{K)X3Pod_=;bM1%6(bef{KN)@BDOz{a^|H zeVZB6 z^=o~ymF(^>2%qNjXuN;_*q_y&B7>J7+054~J-9lg)vie_tkjmRT^!AsQs@`fwRYVe zZd#1+rY}T)KewqcDJa6?}u zWT{a8W31M}-&$T0C|>XUV*A)X>W1s3655pdg48xKRuS_}?#pQvbID2Z9BV0@Hngs{oKY=}>||aO9SZmKcCMxDSv8 rpha%c!Ds-wAjn&Z|L0S2V_$%pO9}Ja{nxJm$dA0NvP`*@N#Oqg-!42> literal 0 HcmV?d00001 diff --git a/services/desktop_app/resources/icon-source.png b/services/desktop_app/resources/icon-source.png new file mode 100644 index 0000000000000000000000000000000000000000..326f5421cd4262adaf573dd72e08304b467d968e GIT binary patch literal 53035 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 RH9x}^*q=6*4L3ZJ{tt(oUhn__ literal 0 HcmV?d00001 diff --git a/services/desktop_app/resources/icon.ico b/services/desktop_app/resources/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..249c451fd41ea38d4cbc6965bd5ce6794d0643fe GIT binary patch literal 91063 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�A(Ld}R9#G)C} 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%0D7ryu|5zI*7_N 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

?lV$|dG>U(!PC#a37-ET;FJ5? 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ȲtkZN!i;9{Eu28A8;Epcng*fyH=F}2 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 literal 0 HcmV?d00001 diff --git a/services/desktop_app/scripts/publish-version.mjs b/services/desktop_app/scripts/publish-version.mjs new file mode 100644 index 0000000..8436a49 --- /dev/null +++ b/services/desktop_app/scripts/publish-version.mjs @@ -0,0 +1,188 @@ +/** + * Publica el instalador NSIS en MinIO y actualiza latest.json + DATA.txt. + * + * Uso: + * set MINIO_ACCESS_KEY=... + * set MINIO_SECRET_KEY=... + * npm run publish:minio + * + * Opcional: + * MINIO_ENDPOINT=https://s3.builker.xyz + * MINIO_BUCKET=apps-versions + * MINIO_PREFIX=IPSExtractor + * RELEASE_DATE=06/07/2026 (formato DATA.txt DD/MM/YYYY; default: hoy o version.json) + */ +import { createHash } from "node:crypto"; +import { createReadStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, ".."); + +/** Carga KEY=VALUE desde un archivo local (sin dependencia dotenv). */ +function loadEnvFile(filePath) { + if (!existsSync(filePath)) return; + const text = readFileSync(filePath, "utf8"); + for (const line of text.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + const eq = trimmed.indexOf("="); + if (eq <= 0) continue; + const key = trimmed.slice(0, eq).trim(); + let value = trimmed.slice(eq + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (!(key in process.env) || process.env[key] === "") { + process.env[key] = value; + } + } +} + +loadEnvFile(join(root, ".env.minio")); +loadEnvFile(join(root, ".env")); + +const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); +const version = pkg.version; +const endpoint = (process.env.MINIO_ENDPOINT || "https://s3.builker.xyz").replace(/\/$/, ""); +const bucket = process.env.MINIO_BUCKET || "apps-versions"; +const prefix = (process.env.MINIO_PREFIX || "IPSExtractor").replace(/^\/|\/$/g, ""); +const accessKey = process.env.MINIO_ACCESS_KEY || process.env.AWS_ACCESS_KEY_ID; +const secretKey = process.env.MINIO_SECRET_KEY || process.env.AWS_SECRET_ACCESS_KEY; + +if (!accessKey || !secretKey) { + console.error( + "Faltan credenciales. Define MINIO_ACCESS_KEY y MINIO_SECRET_KEY (o AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY).", + ); + process.exit(1); +} + +const setupName = `IPSExtractor-Desktop-${version}-Setup.exe`; +const setupPath = join(root, "release", setupName); +if (!existsSync(setupPath)) { + console.error(`No se encontró ${setupPath}. Ejecuta antes: npm run dist`); + process.exit(1); +} + +function todayDisplay() { + const d = new Date(); + const dd = String(d.getDate()).padStart(2, "0"); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const yyyy = d.getFullYear(); + return `${dd}/${mm}/${yyyy}`; +} + +function isoFromDisplay(display) { + // DD/MM/YYYY → YYYY-MM-DD + const m = String(display).trim().match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); + if (!m) return null; + const [, dd, mm, yyyy] = m; + return `${yyyy}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}`; +} + +function displayFromIso(iso) { + const m = String(iso).trim().match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!m) return null; + const [, yyyy, mm, dd] = m; + return `${dd}/${mm}/${yyyy}`; +} + +function resolveReleaseDateDisplay() { + if (process.env.RELEASE_DATE) return String(process.env.RELEASE_DATE).trim(); + try { + const meta = JSON.parse(readFileSync(join(root, "src/shared/version.json"), "utf8")); + if (meta.releaseDate) { + return displayFromIso(meta.releaseDate) || todayDisplay(); + } + } catch { + /* ignore */ + } + return todayDisplay(); +} + +async function sha256File(filePath) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(filePath); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("error", reject); + stream.on("end", () => resolve(hash.digest("hex"))); + }); +} + +async function upload(client, key, body, contentType) { + await client.send( + new PutObjectCommand({ + Bucket: bucket, + Key: key, + Body: body, + ContentType: contentType, + }), + ); + console.log(` ↑ s3://${bucket}/${key}`); +} + +async function main() { + const releaseDateDisplay = resolveReleaseDateDisplay(); + const releaseDateIso = isoFromDisplay(releaseDateDisplay) || new Date().toISOString().slice(0, 10); + const sha256 = await sha256File(setupPath); + const { size } = statSync(setupPath); + + const publicBase = `${endpoint}/${bucket}/${prefix}`; + const objectKeyExe = `${prefix}/${version}/${setupName}`; + const objectKeyData = `${prefix}/${version}/DATA.txt`; + const objectKeyLatest = `${prefix}/latest.json`; + const publicUrl = `${publicBase}/${version}/${setupName}`; + + const latest = { + version, + releaseDate: releaseDateIso, + releaseDateDisplay, + fileName: setupName, + url: publicUrl, + sha256, + size, + }; + + // Actualiza version.json local para alinear la UI con la fecha publicada + writeFileSync( + join(root, "src/shared/version.json"), + `${JSON.stringify({ releaseDate: releaseDateIso }, null, 2)}\n`, + "utf8", + ); + + const client = new S3Client({ + region: process.env.MINIO_REGION || "us-east-1", + endpoint, + forcePathStyle: true, + credentials: { + accessKeyId: accessKey, + secretAccessKey: secretKey, + }, + }); + + console.log(`Publicando v${version} → ${endpoint}/${bucket}/${prefix}/`); + await upload(client, objectKeyExe, createReadStream(setupPath), "application/octet-stream"); + await upload(client, objectKeyData, `${releaseDateDisplay}\n`, "text/plain; charset=utf-8"); + await upload( + client, + objectKeyLatest, + Buffer.from(`${JSON.stringify(latest, null, 2)}\n`, "utf8"), + "application/json", + ); + + console.log("\nListo."); + console.log(` latest.json: ${publicBase}/latest.json`); + console.log(` DATA.txt: ${publicBase}/${version}/DATA.txt → ${releaseDateDisplay}`); + console.log(` Setup: ${publicUrl}`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/services/desktop_app/src/renderer/App.test.tsx b/services/desktop_app/src/renderer/App.test.tsx index cecd6f7..ec19b21 100644 --- a/services/desktop_app/src/renderer/App.test.tsx +++ b/services/desktop_app/src/renderer/App.test.tsx @@ -3,164 +3,49 @@ import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; import App from "./App"; -async function advanceToDiscover(user: ReturnType) { - await user.click(screen.getByRole("button", { name: /explorar/i })); - await user.click(screen.getByRole("button", { name: /siguiente/i })); -} - -async function discoverFields(user: ReturnType) { - await advanceToDiscover(user); - await user.click(screen.getByRole("button", { name: /descubrir campos/i })); -} - describe("App", () => { - it("renders the extraction wizard", () => { + it("renders home shell with jobs and configuration entry", () => { + vi.spyOn(globalThis, "fetch").mockResolvedValue({ + ok: true, + text: async () => "[]", + } as Response); render(); expect(screen.getByText("IPSExtractor")).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /^extraccion$/i })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /tareas/i })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /configuracion/i })).toBeInTheDocument(); - expect(screen.getByText("Origen de los documentos")).toBeInTheDocument(); - expect(screen.queryByText("IPS_DOCUMENT_ROOT_LOCAL")).not.toBeInTheDocument(); - expect(screen.queryByText("IPS_DOCUMENT_ROOT_CONTAINER")).not.toBeInTheDocument(); - expect(screen.queryByText("Ruta enviada a Docker/API")).not.toBeInTheDocument(); - expect(screen.getByRole("button", { name: /siguiente/i })).toBeDisabled(); + expect(screen.getByRole("button", { name: /^trabajos$/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /configuraci[oó]n/i })).toBeInTheDocument(); }); - it("shows API URL settings in the configuration page", async () => { + it("opens configuration as a submenu with connection settings", async () => { const user = userEvent.setup(); + vi.spyOn(globalThis, "fetch").mockResolvedValue({ + ok: true, + text: async () => "[]", + } as Response); render(); - await user.click(screen.getByRole("button", { name: /configuracion/i })); - expect(screen.getByText("Conexion API")).toBeInTheDocument(); + await user.click(screen.getByRole("button", { name: /configuraci[oó]n/i })); + expect( + screen.getByRole("navigation", { name: /secciones de configuraci[oó]n/i }), + ).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^conexi[oó]n$/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^limpieza$/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^backend$/i })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: /^conexi[oó]n$/i })).toBeInTheDocument(); const input = screen.getByLabelText("URL del API Gateway"); await user.clear(input); await user.type(input, "http://localhost:9000"); expect(input).toHaveValue("http://localhost:9000"); }); - it("adds and removes document types", async () => { + it("shows cleaning COP/MOD settings section", async () => { const user = userEvent.setup(); - render(); - await user.click(screen.getByRole("button", { name: /agregar tipo/i })); - expect(screen.getByLabelText("Nombre tipo 2")).toBeInTheDocument(); - const removeButtons = screen.getAllByLabelText("Eliminar tipo"); - await user.click(removeButtons[1]); - expect(screen.queryByLabelText("Nombre tipo 2")).not.toBeInTheDocument(); - }); - - it("does not show keywords in general folder mode", async () => { - const user = userEvent.setup(); - render(); - await user.click(screen.getByRole("button", { name: /carpeta general/i })); - expect(screen.queryByLabelText(/keywords/i)).not.toBeInTheDocument(); - expect(screen.queryByText(/^Keywords$/)).not.toBeInTheDocument(); - expect(screen.getByText(/se procesaran todos los pdf y tiff/i)).toBeInTheDocument(); - }); - - it("shows schema fields from a mocked API response", async () => { - const user = userEvent.setup(); - vi.spyOn(window.desktopApi!, "selectDirectory").mockResolvedValue("C:\\docs\\lote"); vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: true, - text: async () => JSON.stringify({ schema_by_type: { Factura: { numero: "str" } }, sample_by_type: { Factura: { numero: "A1" } } }), + text: async () => "[]", } as Response); render(); - await discoverFields(user); - await user.click(screen.getByRole("button", { name: /siguiente/i })); - expect(await screen.findByRole("checkbox", { name: "Seleccionar numero" })).toBeInTheDocument(); - }); - - it("blocks advancing while no fields are selected", async () => { - const user = userEvent.setup(); - vi.spyOn(window.desktopApi!, "selectDirectory").mockResolvedValue("C:\\docs\\lote"); - vi.spyOn(globalThis, "fetch").mockResolvedValue({ - ok: true, - text: async () => JSON.stringify({ schema_by_type: { Factura: { numero: "str" } }, sample_by_type: { Factura: { numero: "A1" } } }), - } as Response); - render(); - await discoverFields(user); - await user.click(screen.getByRole("button", { name: /siguiente/i })); - expect(screen.getByRole("button", { name: /siguiente/i })).toBeDisabled(); - }); - - it("creates an extraction task with mocked API responses", async () => { - const user = userEvent.setup(); - vi.spyOn(window.desktopApi!, "selectDirectory").mockResolvedValue("C:\\docs\\lote"); - vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => { - const url = String(input); - if (url.includes("/extraction/schemas/upload")) { - return { - ok: true, - text: async () => JSON.stringify({ schema_by_type: { Factura: { numero: "str" } }, sample_by_type: { Factura: { numero: "A1" } } }), - } as Response; - } - if (url.includes("/tasks/extraction")) { - return { - ok: true, - text: async () => JSON.stringify({ - id: "task-1", - type: "extraction", - status: "running", - title: "Extraccion desktop", - request: { source: "desktop" }, - result: { extraction_batch_id: "batch-1", processed: 0, total: 1 }, - error: null, - owner_id: null, - created_at: "now", - updated_at: "now", - started_at: "now", - finished_at: null, - }), - } as Response; - } - if (url.includes("/tasks/task-1/ingest")) { - return { - ok: true, - text: async () => JSON.stringify({ processed: 1, total: 1, task: { id: "task-1", status: "completed", result: { extraction_batch_id: "batch-1" } } }), - } as Response; - } - if (url.includes("/tasks/task-1") && !url.includes("/rows") && !url.includes("/ingest")) { - return { - ok: true, - text: async () => JSON.stringify({ - id: "task-1", - type: "extraction", - status: "completed", - title: "Extraccion desktop", - request: { source: "desktop" }, - result: { extraction_batch_id: "batch-1", processed: 1, total: 1 }, - error: null, - owner_id: null, - created_at: "now", - updated_at: "now", - started_at: "now", - finished_at: "now", - }), - } as Response; - } - if (url.includes("/tasks?")) { - return { - ok: true, - text: async () => JSON.stringify([{ id: "task-1", type: "extraction", status: "completed", title: "Extraccion desktop", request: {}, result: { extraction_batch_id: "batch-1" }, error: null, owner_id: null, created_at: "now", updated_at: "now", started_at: "now", finished_at: "now" }]), - } as Response; - } - if (url.includes("/tasks/task-1/rows")) { - return { - ok: true, - text: async () => JSON.stringify({ task_id: "task-1", batch_id: "batch-1", rows: [{ radicado: "R1", numero: "A1" }], limit: 500, offset: 0 }), - } as Response; - } - return { ok: true, text: async () => "{}" } as Response; - }); - - render(); - await discoverFields(user); - await user.click(screen.getByRole("button", { name: /siguiente/i })); - await user.click(await screen.findByRole("checkbox", { name: "Seleccionar numero" })); - await user.click(screen.getByRole("button", { name: /siguiente/i })); - await user.click(screen.getByRole("button", { name: /ejecutar extraccion/i })); - expect(await screen.findByText("Tareas y resultados")).toBeInTheDocument(); - expect((await screen.findAllByText("completed")).length).toBeGreaterThan(0); - expect(await screen.findByText("A1")).toBeInTheDocument(); + await user.click(screen.getByRole("button", { name: /configuraci[oó]n/i })); + await user.click(screen.getByRole("button", { name: /^limpieza$/i })); + expect(screen.getByRole("heading", { name: /limpieza.*cop/i })).toBeInTheDocument(); + expect(screen.getByLabelText("2024")).toBeInTheDocument(); }); }); diff --git a/services/desktop_app/src/renderer/App.tsx b/services/desktop_app/src/renderer/App.tsx index 14fd387..5bf3fd4 100644 --- a/services/desktop_app/src/renderer/App.tsx +++ b/services/desktop_app/src/renderer/App.tsx @@ -1,17 +1,14 @@ import { BarChart2, Briefcase, - CheckCircle2, Cpu, - Database, Hash, - Loader2, - Server, + Link2, Settings, - XCircle, + Sparkles, } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; -import type { ExtractionFormState, Job } from "../shared/types"; +import type { Job } from "../shared/types"; import { APP_RELEASE_DATE, APP_VERSION, @@ -19,14 +16,18 @@ import { formatVersionLabel, } from "../shared/appVersion"; import { ApiClient } from "./lib/apiClient"; -import { DEFAULT_API_BASE_URL, loadApiBaseUrl, migrateApiBaseUrlInStorage } from "./lib/apiDefaults"; +import { loadApiBaseUrl, migrateApiBaseUrlInStorage } from "./lib/apiDefaults"; import { HomeView } from "./views/HomeView"; import { JobView } from "./views/JobView"; +import { SettingsView, type SettingsSection } from "./views/SettingsView"; import type { JobSection } from "./types/jobSection"; +import appIcon from "./assets/app-icon.png"; +import { UpdatePrompt, useAutoUpdateCheck } from "./components/UpdatePrompt"; // Registrar módulos al cargar la app import "./modules/counting/CountingModule"; import "./modules/extraction/ExtractionModule"; +import "./modules/cleaning/CleaningModule"; type Status = "idle" | "ok" | "error" | "busy"; type View = "home" | "job" | "settings"; @@ -34,15 +35,19 @@ export type { JobSection } from "./types/jobSection"; function App() { const [view, setView] = useState("home"); + const [settingsReturnView, setSettingsReturnView] = useState<"home" | "job">("home"); + const [settingsSection, setSettingsSection] = useState("connection"); const [activeJob, setActiveJob] = useState(null); const [jobSection, setJobSection] = useState("counting"); const [jobSectionContext, setJobSectionContext] = useState>({}); const [apiBaseUrl, setApiBaseUrl] = useState(loadApiBaseUrl); const [connectionStatus, setConnectionStatus] = useState("idle"); - const [connectionMessage, setConnectionMessage] = useState("Sin verificar"); + const [connectionMessage, setConnectionMessage] = useState("Pulsa «Verificar conexión» para comprobar el gateway."); + const [healthJson, setHealthJson] = useState(null); const [configJson, setConfigJson] = useState(null); const [modelsJson, setModelsJson] = useState(null); const [busy, setBusy] = useState(false); + const update = useAutoUpdateCheck(); const client = useMemo(() => new ApiClient(apiBaseUrl), [apiBaseUrl]); @@ -54,21 +59,27 @@ function App() { } }, []); // eslint-disable-line react-hooks/exhaustive-deps - useEffect(() => { localStorage.setItem("apiBaseUrl", apiBaseUrl); }, [apiBaseUrl]); + useEffect(() => { + localStorage.setItem("apiBaseUrl", apiBaseUrl); + }, [apiBaseUrl]); async function verifyConnection() { setConnectionStatus("busy"); - setConnectionMessage("Verificando gateway..."); + setConnectionMessage("Comprobando gateway y servicios…"); setBusy(true); try { const [health, config, models] = await Promise.all([ - client.health(), client.config(), client.models(), + client.health(), + client.config(), + client.models(), ]); + setHealthJson(health); setConfigJson(config); setModelsJson(models); setConnectionStatus("ok"); - setConnectionMessage(`Conectado: ${JSON.stringify(health)}`); + setConnectionMessage("Gateway respondió correctamente."); } catch (err) { + setHealthJson(null); setConnectionStatus("error"); setConnectionMessage(err instanceof Error ? err.message : "No se pudo conectar"); } finally { @@ -88,27 +99,73 @@ function App() { setJobSectionContext(context); } + function openSettings(section: SettingsSection = "connection") { + setSettingsReturnView(view === "job" ? "job" : "home"); + setSettingsSection(section); + setView("settings"); + } + + function leaveSettings() { + if (settingsReturnView === "job" && activeJob) { + setView("job"); + } else { + setView("home"); + } + } + const isJobOpen = view === "job" && activeJob; + const isSettingsOpen = view === "settings"; return (

-
+
{formatVersionLabel()}
- {view === "home" && ( - - )} + {view === "home" && } {view === "job" && activeJob && ( )}
+ + {update.manifest && ( + void update.confirmInstall()} + installing={update.installing} + progress={update.progress} + error={update.error} + /> + )}
); } -type SettingsTab = "config" | "models"; - -function SettingsView({ - apiBaseUrl, - onApiBaseUrlChange, - connectionStatus, - connectionMessage, - verifyConnection, - busy, - configJson, - modelsJson, -}: { - apiBaseUrl: string; - onApiBaseUrlChange: (url: string) => void; - connectionStatus: Status; - connectionMessage: string; - verifyConnection: () => Promise; - busy: boolean; - configJson: unknown; - modelsJson: unknown; -}) { - const [backendTab, setBackendTab] = useState("config"); - - const connectionLabel = - connectionStatus === "ok" - ? "Conectado" - : connectionStatus === "busy" - ? "Verificando..." - : connectionStatus === "error" - ? "Sin conexión" - : "Sin verificar"; - - const backendData = backendTab === "config" ? configJson : modelsJson; - - return ( -
- - -
-
-
- -

Configuración del backend

-
-
- - -
-
-
- {backendData != null ? ( -
{JSON.stringify(backendData, null, 2)}
- ) : ( -
- -

Sin datos del backend

- Verifica la conexión para cargar la {backendTab === "config" ? "configuración" : "lista de modelos"}. -
- )} -
-
-
- ); -} - export default App; diff --git a/services/desktop_app/src/renderer/assets/app-icon.png b/services/desktop_app/src/renderer/assets/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..54863e5c6833e4bfba6f8dab2527a1ee4e4bd230 GIT binary patch literal 56966 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º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 ZZ^TaR%+GyFvnL}x&((F+s#UGu{|^z+;NbuO literal 0 HcmV?d00001 diff --git a/services/desktop_app/src/renderer/components/BackgroundProgressBanner.tsx b/services/desktop_app/src/renderer/components/BackgroundProgressBanner.tsx index 75ff816..e91df2e 100644 --- a/services/desktop_app/src/renderer/components/BackgroundProgressBanner.tsx +++ b/services/desktop_app/src/renderer/components/BackgroundProgressBanner.tsx @@ -13,7 +13,10 @@ function pct(processed: number, total: number): number { export function BackgroundProgressBanner({ progress, compact = false }: Props) { const percent = pct(progress.processed, progress.total); - const moduleLabel = progress.module === "counting" ? "Conteo" : "Extracción"; + const moduleLabel = + progress.module === "counting" ? "Conteo" + : progress.module === "cleaning" ? "Limpieza" + : "Extracción"; return (
diff --git a/services/desktop_app/src/renderer/components/CleaningStepper.tsx b/services/desktop_app/src/renderer/components/CleaningStepper.tsx new file mode 100644 index 0000000..48d23aa --- /dev/null +++ b/services/desktop_app/src/renderer/components/CleaningStepper.tsx @@ -0,0 +1,44 @@ +import { Check } from "lucide-react"; + +export type CleaningStep = 1 | 2 | 3; + +const STEPS: Array<{ id: CleaningStep; label: string }> = [ + { id: 1, label: "Fuente" }, + { id: 2, label: "Reglas" }, + { id: 3, label: "Resultados" }, +]; + +interface CleaningStepperProps { + current: CleaningStep; + maxReached: CleaningStep; + onStepClick?: (step: CleaningStep) => void; +} + +export function CleaningStepper({ current, maxReached, onStepClick }: CleaningStepperProps) { + return ( + + ); +} diff --git a/services/desktop_app/src/renderer/components/CountingWizard.tsx b/services/desktop_app/src/renderer/components/CountingWizard.tsx index 894cdca..31169f4 100644 --- a/services/desktop_app/src/renderer/components/CountingWizard.tsx +++ b/services/desktop_app/src/renderer/components/CountingWizard.tsx @@ -24,6 +24,7 @@ import type { Task, } from "../../shared/types"; import { ApiClient } from "../lib/apiClient"; +import { downloadCsv, toCsv, buildExportCsvFilename } from "../lib/csv"; import { CountResultsTable } from "./CountResultsTable"; import { CountingStepper, type CountingStep } from "./CountingStepper"; @@ -377,25 +378,16 @@ export function CountingWizard({ client, localRoot, containerRoot, job, onStartE function handleExportCsv() { if (!savedFiles.length) return; const headers = ["filename", "radicado", "service_count", "auto_ajustado", "status", "is_valid", "error"]; - const rows = savedFiles.map((f) => - [ - `"${f.filename}"`, - `"${f.radicado ?? ""}"`, - f.service_count ?? "", - autoAdjustedFilenames.has(f.filename) ? "si" : "", - f.status, - f.is_valid === null ? "" : String(f.is_valid), - `"${(f.error ?? "").replace(/"/g, '""')}"`, - ].join(","), - ); - const csv = [headers.join(","), ...rows].join("\n"); - const blob = new Blob([csv], { type: "text/csv" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `conteo-${new Date().toISOString().slice(0, 10)}.csv`; - a.click(); - URL.revokeObjectURL(url); + const rows = savedFiles.map((f) => [ + f.filename, + f.radicado ?? "", + f.service_count ?? "", + autoAdjustedFilenames.has(f.filename) ? "si" : "", + f.status, + f.is_valid === null ? "" : String(f.is_valid), + f.error ?? "", + ]); + downloadCsv(buildExportCsvFilename("conteo", job?.title), toCsv(headers, rows)); } function resetWizard() { diff --git a/services/desktop_app/src/renderer/components/ExtractionResultsTable.tsx b/services/desktop_app/src/renderer/components/ExtractionResultsTable.tsx new file mode 100644 index 0000000..3bbeb20 --- /dev/null +++ b/services/desktop_app/src/renderer/components/ExtractionResultsTable.tsx @@ -0,0 +1,147 @@ +import { Search } from "lucide-react"; +import { useMemo, useState } from "react"; + +type Row = Record; +type FilterKey = "all" | "complete" | "incomplete"; + +interface Props { + rows: Row[]; + columns: string[]; +} + +function cellValue(row: Row, col: string): string { + const v = row[col]; + if (v === null || v === undefined) return ""; + return String(v); +} + +function isEmpty(row: Row, col: string): boolean { + return cellValue(row, col).trim() === ""; +} + +function isComplete(row: Row, columns: string[]): boolean { + return columns.every((c) => !isEmpty(row, c)); +} + +export function ExtractionResultsTable({ rows, columns }: Props) { + const [filter, setFilter] = useState("all"); + const [search, setSearch] = useState(""); + + const summary = useMemo(() => { + const complete = rows.filter((r) => isComplete(r, columns)).length; + return { + total: rows.length, + complete, + incomplete: rows.length - complete, + }; + }, [rows, columns]); + + const filtered = useMemo(() => { + let list = rows; + if (filter === "complete") list = list.filter((r) => isComplete(r, columns)); + else if (filter === "incomplete") list = list.filter((r) => !isComplete(r, columns)); + + const q = search.trim().toLowerCase(); + if (q) { + list = list.filter((r) => + columns.some((c) => cellValue(r, c).toLowerCase().includes(q)), + ); + } + return list; + }, [rows, columns, filter, search]); + + const FILTERS: { key: FilterKey; label: string; count: number }[] = [ + { key: "all", label: "Todos", count: summary.total }, + { key: "complete", label: "Completos", count: summary.complete }, + { key: "incomplete", label: "Incompletos", count: summary.incomplete }, + ]; + + return ( +
+
+
+ {summary.total} + Total +
+
+ {summary.complete} + Completos +
+
+ {summary.incomplete} + Incompletos +
+
+ +
+
+ {FILTERS.map(({ key, label, count }) => ( + + ))} +
+ +
+
+ + setSearch(e.target.value)} + /> +
+
+
+ +
+ + + + {columns.map((c) => ( + + ))} + + + + {filtered.length === 0 ? ( + + + + ) : ( + filtered.map((row, i) => ( + + {columns.map((c) => ( + + ))} + + )) + )} + +
{c}
+ {search + ? "No hay filas que coincidan con la búsqueda." + : "No hay filas en esta categoría."} +
+ {isEmpty(row, c) ? ( + — + ) : ( + cellValue(row, c) + )} +
+
+ + {filtered.length !== rows.length && ( +

+ Mostrando {filtered.length} de {rows.length} filas +

+ )} +
+ ); +} diff --git a/services/desktop_app/src/renderer/components/ExtractionWizard.tsx b/services/desktop_app/src/renderer/components/ExtractionWizard.tsx index 056dee9..c2e2773 100644 --- a/services/desktop_app/src/renderer/components/ExtractionWizard.tsx +++ b/services/desktop_app/src/renderer/components/ExtractionWizard.tsx @@ -15,6 +15,7 @@ import { RefreshCw, Search, Settings2, + Shuffle, Trash2, Zap, } from "lucide-react"; @@ -36,6 +37,9 @@ interface ExtractionWizardProps { statusText: string; selectRoot: () => Promise; discoverSchema: () => Promise; + rediscoverWithOtherSample?: () => Promise; + discoverySampleLabels?: string[]; + canRediscoverWithOtherSample?: boolean; estimate: () => Promise; runExtraction: () => Promise; selection: FieldSelection; @@ -104,6 +108,9 @@ export function ExtractionWizard({ statusText, selectRoot, discoverSchema, + rediscoverWithOtherSample, + discoverySampleLabels = [], + canRediscoverWithOtherSample = false, estimate, runExtraction, selection, @@ -373,7 +380,39 @@ export function ExtractionWizard({ {fieldCount > 0 && (
- {fieldCount} campos detectados. Continua al siguiente paso para elegir cuales exportar. + {fieldCount} campos detectados + {discoverySampleLabels.length > 0 + ? ` con «${discoverySampleLabels.join(", ")}».` + : "."}{" "} + Continúa al siguiente paso para elegir cuáles exportar. +
+ )} + + {schemaResponse && rediscoverWithOtherSample && ( +
+
+ ¿Algo no salió como esperabas? + + Vuelve a descubrir los campos usando otro archivo aleatorio de la carpeta. + {discoverySampleLabels.length > 0 && ( + <> Muestra actual: {discoverySampleLabels.join(", ")}. + )} + +
+
)}
@@ -424,6 +463,34 @@ export function ExtractionWizard({ />
+ {schemaResponse && rediscoverWithOtherSample && ( +
+
+ ¿Algo no salió como esperabas? + + Reintenta el descubrimiento con otro archivo aleatorio + {discoverySampleLabels.length > 0 && ( + <> (ahora: {discoverySampleLabels.join(", ")}) + )} + . + +
+ +
+ )}

Vista previa {selection.fields.length > 0 && ( diff --git a/services/desktop_app/src/renderer/components/UpdatePrompt.tsx b/services/desktop_app/src/renderer/components/UpdatePrompt.tsx new file mode 100644 index 0000000..77e4da7 --- /dev/null +++ b/services/desktop_app/src/renderer/components/UpdatePrompt.tsx @@ -0,0 +1,172 @@ +import { Download, Loader2, X } from "lucide-react"; +import { useEffect, useState } from "react"; +import type { UpdateManifest, UpdateProgress } from "../../shared/types"; + +type Props = { + open: boolean; + currentVersion: string; + manifest: UpdateManifest; + onClose: () => void; + onConfirm: () => void; + installing: boolean; + progress: UpdateProgress | null; + error: string | null; +}; + +function formatBytes(n: number | undefined): string { + if (!n || n <= 0) return ""; + if (n < 1024) return `${n} B`; + if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; + return `${(n / (1024 * 1024)).toFixed(1)} MB`; +} + +export function UpdatePrompt({ + open, + currentVersion, + manifest, + onClose, + onConfirm, + installing, + progress, + error, +}: Props) { + if (!open) return null; + + const dateLabel = manifest.releaseDateDisplay || manifest.releaseDate || ""; + const percent = progress?.percent ?? 0; + const phaseLabel = + progress?.phase === "verifying" + ? "Verificando archivo…" + : progress?.phase === "installing" + ? "Iniciando instalador…" + : progress?.phase === "downloading" + ? `Descargando… ${percent}%` + : null; + + return ( +
+
+
+

Nueva versión disponible

+ {!installing && ( + + )} +
+ +

+ Hay una versión nueva: v{manifest.version} + {dateLabel ? <> (lanzamiento {dateLabel}) : null}. +
+ Tienes instalada la v{currentVersion}. +

+ + {manifest.size != null && manifest.size > 0 && ( +

Tamaño aproximado: {formatBytes(manifest.size)}

+ )} + + {installing && ( +
+
+
+
+ + {phaseLabel || "Preparando…"} + {progress?.phase === "downloading" && progress.total + ? ` · ${formatBytes(progress.received)} / ${formatBytes(progress.total)}` + : ""} + +
+ )} + + {error && ( +
+ {error} +
+ )} + +
+ + +
+
+
+ ); +} + +/** Comprueba actualizaciones al montar (solo si existe desktopApi). */ +export function useAutoUpdateCheck() { + const [open, setOpen] = useState(false); + const [currentVersion, setCurrentVersion] = useState(""); + const [manifest, setManifest] = useState(null); + const [installing, setInstalling] = useState(false); + const [progress, setProgress] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const api = window.desktopApi; + if (!api?.checkForUpdates) return; + + let cancelled = false; + const timer = window.setTimeout(() => { + void (async () => { + try { + const result = await api.checkForUpdates(); + if (cancelled || !result.updateAvailable || !result.manifest) return; + setCurrentVersion(result.currentVersion); + setManifest(result.manifest); + setOpen(true); + } catch { + /* silencioso */ + } + })(); + }, 2000); + + return () => { + cancelled = true; + window.clearTimeout(timer); + }; + }, []); + + useEffect(() => { + const api = window.desktopApi; + if (!api?.onUpdateProgress || !installing) return; + return api.onUpdateProgress((p) => setProgress(p)); + }, [installing]); + + async function confirmInstall() { + if (!manifest || !window.desktopApi?.installUpdate) return; + setInstalling(true); + setError(null); + setProgress({ phase: "downloading", percent: 0 }); + try { + await window.desktopApi.installUpdate(manifest); + // La app debería cerrarse desde main; si no, mostramos mensaje. + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + setInstalling(false); + } + } + + return { + open, + currentVersion, + manifest, + installing, + progress, + error, + close: () => { + if (!installing) setOpen(false); + }, + confirmInstall, + }; +} diff --git a/services/desktop_app/src/renderer/lib/apiClient.ts b/services/desktop_app/src/renderer/lib/apiClient.ts index 8e0a43f..b5184e1 100644 --- a/services/desktop_app/src/renderer/lib/apiClient.ts +++ b/services/desktop_app/src/renderer/lib/apiClient.ts @@ -1,4 +1,10 @@ import type { + CleaningAnalysisResponse, + CleaningJob, + CleaningPreviewPayload, + CleaningPreviewResponse, + CleaningRowsResponse, + CleaningRuleInfo, CountJobFile, CountSyncResponse, CreateCountJobPayload, @@ -7,6 +13,7 @@ import type { ExtractionFromCountPayload, Job, RowsResponse, + RunCleaningPayload, SchemaResponse, Task, UpdateJobPayload, @@ -51,7 +58,9 @@ export class ApiClient { return ( path.startsWith("/count/") || path.includes("/ingest") || - path.includes("/upload") + path.includes("/upload") || + path.startsWith("/cleaning/analyze-csv") || + path.startsWith("/cleaning/jobs/from-csv") ); } @@ -218,6 +227,51 @@ export class ApiClient { return this.postJson("/tasks/extraction/from-count", payload); } + // ── Cleaning ─────────────────────────────────────────────────────────────── + + async cleaningRules(): Promise { + return this.getJson("/cleaning/rules"); + } + + async analyzeCleaningBatch(batchId: string): Promise { + return this.getJson( + `/cleaning/batches/${encodeURIComponent(batchId)}/analysis`, + ); + } + + async analyzeCleaningCsv(file: File | Blob, filename = "upload.csv"): Promise { + const form = new FormData(); + form.append("file", file, filename); + return this.postForm("/cleaning/analyze-csv", form); + } + + async runCleaning(payload: RunCleaningPayload): Promise { + return this.postJson("/cleaning/jobs", payload); + } + + async previewCleaning(payload: CleaningPreviewPayload): Promise { + return this.postJson("/cleaning/preview", payload); + } + + async runCleaningFromCsv(form: FormData): Promise { + return this.postForm("/cleaning/jobs/from-csv", form); + } + + async cleaningJobs(jobId?: string): Promise { + const qs = jobId ? `?job_id=${encodeURIComponent(jobId)}` : ""; + return this.getJson(`/cleaning/jobs${qs}`); + } + + async cleaningJob(cleaningJobId: string): Promise { + return this.getJson(`/cleaning/jobs/${encodeURIComponent(cleaningJobId)}`); + } + + async cleaningJobRows(cleaningJobId: string, limit = 500, offset = 0): Promise { + return this.getJson( + `/cleaning/jobs/${encodeURIComponent(cleaningJobId)}/rows?limit=${limit}&offset=${offset}`, + ); + } + private async handle(response: Response): Promise { const text = await response.text(); let data: unknown = null; diff --git a/services/desktop_app/src/renderer/lib/cleaningSettings.test.ts b/services/desktop_app/src/renderer/lib/cleaningSettings.test.ts new file mode 100644 index 0000000..7c04599 --- /dev/null +++ b/services/desktop_app/src/renderer/lib/cleaningSettings.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it, beforeEach } from "vitest"; +import { + defaultCopModConfig, + loadCopModConfig, + saveCopModConfig, + CLEANING_COPMOD_STORAGE_KEY, +} from "./cleaningSettings"; + +describe("cleaningSettings", () => { + beforeEach(() => { + localStorage.removeItem(CLEANING_COPMOD_STORAGE_KEY); + }); + + it("returns defaults when empty", () => { + const cfg = loadCopModConfig(); + expect(cfg.moderadora_by_year["2024"]).toEqual([4500, 18200, 47700]); + }); + + it("persists and reloads custom years", () => { + saveCopModConfig({ + moderadora_by_year: { "2030": [1000, 2000] }, + }); + const cfg = loadCopModConfig(); + expect(cfg.moderadora_by_year).toEqual({ "2030": [1000, 2000] }); + }); + + it("defaultCopModConfig is a deep copy", () => { + const a = defaultCopModConfig(); + a.moderadora_by_year["2024"].push(999); + expect(defaultCopModConfig().moderadora_by_year["2024"]).not.toContain(999); + }); +}); diff --git a/services/desktop_app/src/renderer/lib/cleaningSettings.ts b/services/desktop_app/src/renderer/lib/cleaningSettings.ts new file mode 100644 index 0000000..4101d36 --- /dev/null +++ b/services/desktop_app/src/renderer/lib/cleaningSettings.ts @@ -0,0 +1,66 @@ +/** Preferencias locales de limpieza (COP/MOD). */ + +export const CLEANING_COPMOD_STORAGE_KEY = "cleaningCopModConfig"; + +/** Valores por defecto alineados con el backend (`DEFAULT_MODERADORA_BY_YEAR`). */ +export const DEFAULT_MODERADORA_BY_YEAR: Record = { + "2023": [4100, 16400, 43000], + "2024": [4500, 18200, 47700], + "2025": [4700, 19200, 50300], + "2026": [5000, 20100, 52800], +}; + +export type CopModConfig = { + moderadora_by_year: Record; +}; + +export function defaultCopModConfig(): CopModConfig { + return { + moderadora_by_year: Object.fromEntries( + Object.entries(DEFAULT_MODERADORA_BY_YEAR).map(([y, vals]) => [y, [...vals]]), + ), + }; +} + +function normalizeYearMap(raw: unknown): Record { + if (!raw || typeof raw !== "object") return {}; + const out: Record = {}; + for (const [k, v] of Object.entries(raw as Record)) { + const year = String(Number.parseInt(k, 10)); + if (!Number.isFinite(Number(year)) || year === "NaN") continue; + if (!Array.isArray(v)) continue; + const nums = v + .map((x) => Math.round(Number(x))) + .filter((n) => Number.isFinite(n) && n >= 0); + if (nums.length > 0) { + out[year] = [...new Set(nums)].sort((a, b) => a - b); + } + } + return out; +} + +export function loadCopModConfig(): CopModConfig { + try { + const raw = localStorage.getItem(CLEANING_COPMOD_STORAGE_KEY); + if (!raw) return defaultCopModConfig(); + const parsed = JSON.parse(raw) as { moderadora_by_year?: unknown }; + const years = normalizeYearMap(parsed?.moderadora_by_year); + if (Object.keys(years).length === 0) return defaultCopModConfig(); + return { moderadora_by_year: years }; + } catch { + return defaultCopModConfig(); + } +} + +export function saveCopModConfig(config: CopModConfig): void { + const years = normalizeYearMap(config.moderadora_by_year); + localStorage.setItem( + CLEANING_COPMOD_STORAGE_KEY, + JSON.stringify({ moderadora_by_year: years }), + ); +} + +/** Payload listo para enviar al API de cleaning. */ +export function copModConfigForApi(config?: CopModConfig | null): CopModConfig { + return config ?? loadCopModConfig(); +} diff --git a/services/desktop_app/src/renderer/lib/csv.test.ts b/services/desktop_app/src/renderer/lib/csv.test.ts index 8992a45..b3fa69f 100644 --- a/services/desktop_app/src/renderer/lib/csv.test.ts +++ b/services/desktop_app/src/renderer/lib/csv.test.ts @@ -1,18 +1,40 @@ import { describe, expect, it } from "vitest"; -import { rowsToCsv } from "./csv"; +import { + buildExportCsvFilename, + csvEscape, + rowsToCsv, + sanitizeFilenamePart, + toCsv, +} from "./csv"; describe("csv export", () => { - it("escapes commas, quotes and newlines", () => { + it("usa pipe como separador", () => { + const csv = toCsv(["a", "b"], [["1", "2"], ["3", "4"]]); + expect(csv).toBe("a|b\n1|2\n3|4"); + }); + + it("escapa pipes, comillas y saltos de línea", () => { const csv = rowsToCsv( [ { radicado: "R1", - nombre: 'Ana, "IPS"', + nombre: 'Ana| "IPS"', nota: "linea 1\nlinea 2", }, ], { nombre_original: "nombre" }, ); - expect(csv).toContain('R1,"Ana, ""IPS""","linea 1\nlinea 2"'); + expect(csv).toContain('R1|"Ana| ""IPS"""|"linea 1\nlinea 2"'); + }); + + it("no escapa comas (no son el delimitador)", () => { + expect(csvEscape("Ana, IPS")).toBe("Ana, IPS"); + }); + + it("incluye el título del trabajo en el nombre de descarga", () => { + expect(sanitizeFilenamePart("Lote Abril / 2026")).toBe("Lote-Abril-2026"); + expect(buildExportCsvFilename("limpieza", "Lote Abril", "validos")).toMatch( + /^limpieza-Lote-Abril-validos-\d{4}-\d{2}-\d{2}\.csv$/, + ); }); }); diff --git a/services/desktop_app/src/renderer/lib/csv.ts b/services/desktop_app/src/renderer/lib/csv.ts index c6f32b2..44c9bbc 100644 --- a/services/desktop_app/src/renderer/lib/csv.ts +++ b/services/desktop_app/src/renderer/lib/csv.ts @@ -1,4 +1,6 @@ -const NEEDS_QUOTES = /[",\r\n]/; +export const CSV_DELIMITER = "|"; + +const NEEDS_QUOTES = /["|\r\n]/; export function csvEscape(value: unknown): string { if (value === null || value === undefined) return ""; @@ -7,13 +9,21 @@ export function csvEscape(value: unknown): string { return `"${text.replace(/"/g, '""')}"`; } +/** Serializa encabezado + filas con separador pipe (`|`). */ +export function toCsv(headers: string[], rows: Array>): string { + const lines = [ + headers.map(csvEscape).join(CSV_DELIMITER), + ...rows.map((row) => row.map(csvEscape).join(CSV_DELIMITER)), + ]; + return lines.join("\n"); +} + export function rowsToCsv(rows: Array>, aliasMap: Record = {}): string { const header = inferHeader(rows, aliasMap); - const lines = [header.map(csvEscape).join(",")]; - for (const row of rows) { - lines.push(header.map((column) => csvEscape(row[column])).join(",")); - } - return `${lines.join("\r\n")}\r\n`; + return toCsv( + header, + rows.map((row) => header.map((column) => row[column])), + ); } export function inferHeader(rows: Array>, aliasMap: Record = {}): string[] { @@ -38,3 +48,43 @@ export function inferHeader(rows: Array>, aliasMap: Reco } return header; } + +export function downloadCsv(filename: string, csv: string): void { + const blob = new Blob([csv], { type: "text/csv;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +} + +/** Normaliza el título del trabajo para usarlo en nombres de archivo. */ +export function sanitizeFilenamePart(value: string | null | undefined, fallback = "sin-titulo"): string { + const cleaned = String(value ?? "") + .normalize("NFKD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/[^a-zA-Z0-9._-]+/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") + .slice(0, 80); + return cleaned || fallback; +} + +/** + * Nombre de descarga: `{servicio}-{titulo-trabajo}[-{extra}]-{YYYY-MM-DD}.csv` + * Ej.: `limpieza-lote-abril-2026-07-10.csv` + */ +export function buildExportCsvFilename( + service: string, + jobTitle?: string | null, + extra?: string | null, +): string { + const parts = [ + sanitizeFilenamePart(service, "export"), + sanitizeFilenamePart(jobTitle, "sin-titulo"), + ]; + if (extra) parts.push(sanitizeFilenamePart(extra)); + parts.push(new Date().toISOString().slice(0, 10)); + return `${parts.join("-")}.csv`; +} diff --git a/services/desktop_app/src/renderer/modules/cleaning/CleaningModule.ts b/services/desktop_app/src/renderer/modules/cleaning/CleaningModule.ts new file mode 100644 index 0000000..1570534 --- /dev/null +++ b/services/desktop_app/src/renderer/modules/cleaning/CleaningModule.ts @@ -0,0 +1,18 @@ +import { CleaningWizardModule } from "./CleaningWizardModule"; +import { CleaningResults } from "./CleaningResults"; +import type { ModuleDescriptor } from "../registry"; +import { registerModule } from "../registry"; +import type { Task } from "../../../shared/types"; + +const CleaningModule: ModuleDescriptor = { + type: "cleaning", + label: "Limpieza", + iconName: "Sparkles", + hasTask: (tasks: Task[]) => tasks.some((t) => t.type === "cleaning"), + Wizard: CleaningWizardModule, + Results: CleaningResults, +}; + +registerModule(CleaningModule); + +export { CleaningModule }; diff --git a/services/desktop_app/src/renderer/modules/cleaning/CleaningResults.tsx b/services/desktop_app/src/renderer/modules/cleaning/CleaningResults.tsx new file mode 100644 index 0000000..2a356d1 --- /dev/null +++ b/services/desktop_app/src/renderer/modules/cleaning/CleaningResults.tsx @@ -0,0 +1,125 @@ +import { Download, Loader2, RefreshCw, Sparkles } from "lucide-react"; +import { useEffect, useState } from "react"; +import type { CleaningJob } from "../../../shared/types"; +import { ExtractionResultsTable } from "../../components/ExtractionResultsTable"; +import { buildExportCsvFilename, downloadCsv, toCsv } from "../../lib/csv"; +import type { ModuleResultsProps } from "../registry"; + +type Row = Record; + +export function CleaningResults({ client, task, job }: ModuleResultsProps) { + const [cleaningJob, setCleaningJob] = useState(null); + const [rows, setRows] = useState([]); + const [columns, setColumns] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + async function load() { + try { + const cleaningJobId = String( + (task.result as Record)?.cleaning_job_id ?? "", + ); + if (!cleaningJobId) { + setError("La tarea no tiene cleaning_job_id en result"); + return; + } + const jobData = await client.cleaningJob(cleaningJobId); + setCleaningJob(jobData); + if (jobData.status === "completed") { + const response = await client.cleaningJobRows(cleaningJobId, 500, 0); + const data = response.rows ?? []; + setRows(data); + setColumns(Array.from(new Set(data.flatMap((r) => Object.keys(r))))); + } + } catch (e) { + setError(String(e instanceof Error ? e.message : e)); + } finally { + setLoading(false); + } + } + + useEffect(() => { + void load(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [task.id]); + + function exportCsv() { + const csv = toCsv( + columns, + rows.map((r) => columns.map((h) => r[h])), + ); + downloadCsv(buildExportCsvFilename("limpieza", job.title), csv); + } + + if (loading) { + return ( +
+ + Cargando resultados de limpieza… +
+ ); + } + + if (error) { + return ( +
+ {error} + +
+ ); + } + + return ( +
+
+
+

+ + {task.title ?? "Limpieza"} +

+ + {cleaningJob?.status ?? task.status} + + {cleaningJob?.source && ( + + · {cleaningJob.source === "csv" ? "CSV" : "extracción"} + + )} +
+ {rows.length > 0 && ( + + )} +
+ + {cleaningJob?.log && cleaningJob.log.length > 0 && ( +
+ Log ({cleaningJob.log.length}) +
    + {cleaningJob.log.map((line, i) => ( +
  • {line}
  • + ))} +
+
+ )} + + {rows.length === 0 ? ( +
+

No se encontraron filas limpiadas.

+
+ ) : ( + + )} +
+ ); +} diff --git a/services/desktop_app/src/renderer/modules/cleaning/CleaningWizard.tsx b/services/desktop_app/src/renderer/modules/cleaning/CleaningWizard.tsx new file mode 100644 index 0000000..fb20cb0 --- /dev/null +++ b/services/desktop_app/src/renderer/modules/cleaning/CleaningWizard.tsx @@ -0,0 +1,967 @@ +import { + AlertCircle, + ArrowRight, + CheckCircle2, + Cpu, + Download, + FileSpreadsheet, + Loader2, + Play, + Sparkles, + Upload, +} from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import type { + BackgroundProgress, + CleaningAnalysisResponse, + CleaningColumnAnalysis, + CleaningJob, + CleaningPreviewColumn, + CleaningPreviewResponse, + CleaningRuleInfo, + Job, + Task, +} from "../../../shared/types"; +import { CleaningStepper, type CleaningStep } from "../../components/CleaningStepper"; +import { ExtractionResultsTable } from "../../components/ExtractionResultsTable"; +import type { ApiClient } from "../../lib/apiClient"; +import { loadCopModConfig } from "../../lib/cleaningSettings"; +import { downloadCsv, toCsv, buildExportCsvFilename } from "../../lib/csv"; + +type SourceMode = "extraction" | "csv"; + +interface Props { + client: ApiClient; + job: Job; + extractionBatchId?: string; + extractionTaskId?: string; + onTaskCreated?: (task: Task) => void; + onProgress?: (progress: BackgroundProgress) => void; +} + +function batchIdFromTask(task: Task): string { + return String((task.result as Record)?.extraction_batch_id ?? ""); +} + +function formatTaskDate(iso: string): string { + try { + return new Date(iso).toLocaleString("es-CO", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + } catch { + return iso; + } +} + +export function CleaningWizard({ + client, + job, + extractionBatchId: initialBatchId, + extractionTaskId: initialTaskId, + onTaskCreated, + onProgress, +}: Props) { + const hasHandoff = Boolean(initialBatchId || initialTaskId); + const [step, setStep] = useState(1); + const [maxReached, setMaxReached] = useState(1); + const [source, setSource] = useState(hasHandoff ? "extraction" : "extraction"); + const [extractionTasks, setExtractionTasks] = useState([]); + const [loadingTasks, setLoadingTasks] = useState(true); + const [selectedTaskId, setSelectedTaskId] = useState(initialTaskId ?? null); + const [batchId, setBatchId] = useState(initialBatchId ?? ""); + const [csvFile, setCsvFile] = useState(null); + const [rulesCatalog, setRulesCatalog] = useState([]); + const [analysis, setAnalysis] = useState(null); + const [rulesPerColumn, setRulesPerColumn] = useState>({}); + const [copModFechaColumn, setCopModFechaColumn] = useState>({}); + const [loading, setLoading] = useState(false); + const [running, setRunning] = useState(false); + const [error, setError] = useState(null); + const [resultJob, setResultJob] = useState(null); + const [resultRows, setResultRows] = useState[]>([]); + const [handoffAnalyzed, setHandoffAnalyzed] = useState(false); + const [preview, setPreview] = useState(null); + const [previewLoading, setPreviewLoading] = useState(false); + const [previewError, setPreviewError] = useState(null); + const [activeColumn, setActiveColumn] = useState(null); + const [sampleMode, setSampleMode] = useState<"before" | "after">("before"); + const fileInputRef = useRef(null); + const previewTimerRef = useRef | null>(null); + + function goToStep(next: CleaningStep) { + setStep(next); + setMaxReached((m) => (next > m ? next : m)); + } + + useEffect(() => { + client.cleaningRules().then(setRulesCatalog).catch(() => setRulesCatalog([])); + }, [client]); + + useEffect(() => { + let cancelled = false; + async function loadExtractions() { + setLoadingTasks(true); + try { + const tasks = await client.jobTasks(job.id); + if (cancelled) return; + const extractions = tasks + .filter((t) => t.type === "extraction" && t.status === "completed") + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + setExtractionTasks(extractions); + + // Preselección por handoff o la más reciente + if (initialTaskId && extractions.some((t) => t.id === initialTaskId)) { + const t = extractions.find((x) => x.id === initialTaskId)!; + setSelectedTaskId(t.id); + setBatchId(batchIdFromTask(t) || initialBatchId || ""); + setSource("extraction"); + } else if (initialBatchId) { + const match = extractions.find((t) => batchIdFromTask(t) === initialBatchId); + if (match) { + setSelectedTaskId(match.id); + setBatchId(initialBatchId); + } else { + setBatchId(initialBatchId); + } + setSource("extraction"); + } else if (extractions.length > 0 && !selectedTaskId) { + const newest = extractions[0]; + setSelectedTaskId(newest.id); + setBatchId(batchIdFromTask(newest)); + } + } catch { + if (!cancelled) setExtractionTasks([]); + } finally { + if (!cancelled) setLoadingTasks(false); + } + } + void loadExtractions(); + return () => { cancelled = true; }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [client, job.id, initialBatchId, initialTaskId]); + + // Handoff: analizar automáticamente al llegar desde Resultados + useEffect(() => { + if (!hasHandoff || handoffAnalyzed || loadingTasks) return; + const id = batchId || initialBatchId; + if (!id) return; + setHandoffAnalyzed(true); + void loadAnalysis("extraction", id, null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasHandoff, handoffAnalyzed, loadingTasks, batchId, initialBatchId]); + + function selectExtraction(task: Task) { + setSelectedTaskId(task.id); + setBatchId(batchIdFromTask(task)); + setSource("extraction"); + setError(null); + } + + function ruleName(id: string): string { + return rulesCatalog.find((r) => r.id === id)?.name ?? id; + } + + function guessFechaColumn(columns: CleaningColumnAnalysis[], amountCol: string): string { + const candidates = columns.filter( + (c) => + c.name !== amountCol && + (c.inferred_type === "date" || + /fecha|date|servicio|fec[_ ]/i.test(c.name)), + ); + const preferred = + candidates.find((c) => /fecha\s*servicio|fec[_ ]?serv/i.test(c.name)) ?? + candidates.find((c) => c.inferred_type === "date") ?? + candidates[0]; + return preferred?.name ?? ""; + } + + function toggleRule(column: string, ruleId: string) { + setRulesPerColumn((prev) => { + const current = prev[column] ?? []; + const turningOn = !current.includes(ruleId); + const next = turningOn + ? [...current, ruleId] + : current.filter((r) => r !== ruleId); + return { ...prev, [column]: next }; + }); + if (ruleId === "cop_mod_split") { + setCopModFechaColumn((prev) => { + const current = rulesPerColumn[column] ?? []; + const turningOn = !current.includes(ruleId); + if (!turningOn) { + const next = { ...prev }; + delete next[column]; + return next; + } + if (prev[column]) return prev; + const guess = analysis ? guessFechaColumn(analysis.columns, column) : ""; + return guess ? { ...prev, [column]: guess } : prev; + }); + } + setSampleMode("after"); + } + + function suggestRules(columns: CleaningColumnAnalysis[]) { + const suggested: Record = {}; + for (const col of columns) { + const applicable = col.applicable_rules ?? []; + suggested[col.name] = applicable.includes("strip_spaces") ? ["strip_spaces"] : []; + } + setRulesPerColumn(suggested); + setCopModFechaColumn({}); + } + + function cleaningExtraPayload(): { + cop_mod_fecha_column?: Record; + cop_mod_config: ReturnType; + } { + const fechaEntries = Object.entries(copModFechaColumn).filter( + ([col, fecha]) => + Boolean(fecha) && (rulesPerColumn[col] ?? []).includes("cop_mod_split"), + ); + return { + ...(fechaEntries.length > 0 + ? { cop_mod_fecha_column: Object.fromEntries(fechaEntries) } + : {}), + cop_mod_config: loadCopModConfig(), + }; + } + + function previewColumn(name: string): CleaningPreviewColumn | undefined { + return preview?.columns.find((c) => c.name === name); + } + + function displaySamples(col: CleaningColumnAnalysis): string[] { + if (col.samples && col.samples.length > 0) return col.samples; + if (col.example) return [String(col.example)]; + return []; + } + + /** Columnas nuevas respecto al análisis (p. ej. *_doc_tipo / *_doc_numeros). */ + function derivedColumnNames(sourceCol: string): string[] { + if (!preview) return []; + const original = new Set(analysis?.columns.map((c) => c.name) ?? []); + return preview.columns + .map((c) => c.name) + .filter( + (name) => + !original.has(name) && + name.toLowerCase().startsWith(`${sourceCol.toLowerCase()}_`), + ); + } + + /** Orden: columna activa → columnas derivadas → resto (sin cortar). */ + function orderedSampleColumnNames(mode: "before" | "after"): string[] { + const analysisNames = analysis?.columns.map((c) => c.name) ?? []; + const names = + mode === "after" && preview + ? preview.columns.map((c) => c.name) + : analysisNames; + const focus = activeColumn; + if (!focus || !names.includes(focus)) return names; + const derived = names.filter( + (n) => n !== focus && n.toLowerCase().startsWith(`${focus.toLowerCase()}_`), + ); + const rest = names.filter((n) => n !== focus && !derived.includes(n)); + return [focus, ...derived, ...rest]; + } + + // Preview en vivo al cambiar reglas (debounce 300ms) + useEffect(() => { + if (step !== 2 || !analysis) return; + const sampleRows = analysis.sample_rows ?? []; + if (previewTimerRef.current) clearTimeout(previewTimerRef.current); + + previewTimerRef.current = setTimeout(() => { + void (async () => { + setPreviewLoading(true); + setPreviewError(null); + try { + const extra = cleaningExtraPayload(); + const payload = + sampleRows.length > 0 + ? { rows: sampleRows, rules_per_column: rulesPerColumn, ...extra } + : batchId + ? { + extraction_batch_id: batchId, + rules_per_column: rulesPerColumn, + ...extra, + } + : null; + if (!payload) { + setPreview(null); + return; + } + const data = await client.previewCleaning(payload); + setPreview(data); + } catch (e) { + setPreviewError(String(e instanceof Error ? e.message : e)); + } finally { + setPreviewLoading(false); + } + })(); + }, 300); + + return () => { + if (previewTimerRef.current) clearTimeout(previewTimerRef.current); + }; + }, [step, analysis, rulesPerColumn, copModFechaColumn, batchId, client]); + + async function loadAnalysis(mode: SourceMode, batch?: string, file?: File | null) { + setLoading(true); + setError(null); + setAnalysis(null); + onProgress?.({ + module: "cleaning", + total: 1, + processed: 0, + failed: 0, + currentFile: mode === "csv" ? (file?.name ?? "CSV") : "extracción", + running: true, + label: "Analizando columnas…", + }); + try { + let data: CleaningAnalysisResponse; + if (mode === "extraction") { + if (!batch) throw new Error("Selecciona una extracción de la lista"); + data = await client.analyzeCleaningBatch(batch); + } else { + if (!file) throw new Error("Selecciona un archivo CSV"); + data = await client.analyzeCleaningCsv(file, file.name); + } + setAnalysis(data); + suggestRules(data.columns); + setActiveColumn(data.columns[0]?.name ?? null); + setSampleMode("before"); + setPreview(null); + goToStep(2); + } catch (e) { + setError(String(e instanceof Error ? e.message : e)); + } finally { + setLoading(false); + onProgress?.({ + module: "cleaning", + total: 1, + processed: 1, + failed: 0, + currentFile: "", + running: false, + label: "", + }); + } + } + + async function handleAnalyze() { + await loadAnalysis(source, batchId, csvFile); + } + + async function handleRun() { + setRunning(true); + setError(null); + setResultJob(null); + setResultRows([]); + onProgress?.({ + module: "cleaning", + total: 1, + processed: 0, + failed: 0, + currentFile: source === "csv" ? (csvFile?.name ?? "CSV") : "extracción", + running: true, + label: "Aplicando reglas de limpieza…", + }); + try { + let cleaningJob: CleaningJob; + if (source === "extraction") { + if (!batchId) throw new Error("Selecciona una extracción"); + cleaningJob = await client.runCleaning({ + extraction_batch_id: batchId, + job_id: job.id, + rules_per_column: rulesPerColumn, + ...cleaningExtraPayload(), + }); + } else { + if (!csvFile) throw new Error("Selecciona un archivo CSV"); + const form = new FormData(); + const extra = cleaningExtraPayload(); + form.append("file", csvFile, csvFile.name); + form.append("job_id", job.id); + form.append("rules_per_column", JSON.stringify(rulesPerColumn)); + if (extra.cop_mod_fecha_column) { + form.append("cop_mod_fecha_column", JSON.stringify(extra.cop_mod_fecha_column)); + } + form.append("cop_mod_config", JSON.stringify(extra.cop_mod_config)); + cleaningJob = await client.runCleaningFromCsv(form); + } + setResultJob(cleaningJob); + if (cleaningJob.status === "completed") { + const rowsResp = await client.cleaningJobRows(cleaningJob.id, 500, 0); + setResultRows(rowsResp.rows ?? []); + } + try { + const tasks = await client.jobTasks(job.id); + const latest = tasks + .filter((t) => t.type === "cleaning") + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0]; + if (latest) onTaskCreated?.(latest); + } catch { /* silent */ } + goToStep(3); + } catch (e) { + setError(String(e instanceof Error ? e.message : e)); + } finally { + setRunning(false); + onProgress?.({ + module: "cleaning", + total: 1, + processed: 1, + failed: 0, + currentFile: "", + running: false, + label: "", + }); + } + } + + function exportCsv() { + if (resultRows.length === 0) return; + const columns = Array.from(new Set(resultRows.flatMap((r) => Object.keys(r)))); + const csv = toCsv( + columns, + resultRows.map((r) => columns.map((h) => r[h])), + ); + downloadCsv(buildExportCsvFilename("limpieza", job.title), csv); + } + + function resetWizard() { + setStep(1); + setMaxReached(1); + setResultJob(null); + setResultRows([]); + setAnalysis(null); + setHandoffAnalyzed(true); // no re-auto-analizar + setError(null); + } + + const resultColumns = + resultRows.length > 0 + ? Array.from(new Set(resultRows.flatMap((r) => Object.keys(r)))) + : []; + + const selectedExtraction = extractionTasks.find((t) => t.id === selectedTaskId) ?? null; + const canAnalyze = + source === "extraction" ? Boolean(batchId) : Boolean(csvFile); + + return ( +
+ + +
+ {error && ( +
+ {error} +
+ )} + + {/* ── PASO 1: Fuente ── */} + {step === 1 && ( +
+
+

Origen de los datos

+

+ Elige una extracción completada de este trabajo o sube un CSV para limpiar. +

+
+ +
+
+ + +
+
+ + {source === "extraction" ? ( +
+ + {loadingTasks ? ( +
+ Cargando extracciones… +
+ ) : extractionTasks.length === 0 ? ( +
+ No hay extracciones completadas en este trabajo. + +
+ ) : ( +
+ {extractionTasks.map((task, index) => { + const selected = task.id === selectedTaskId; + return ( + + ); + })} +
+ )} + {selectedExtraction && ( +

+ Seleccionada: {selectedExtraction.title ?? "Extracción"} +

+ )} +
+ ) : ( +
+ + setCsvFile(e.target.files?.[0] ?? null)} + /> +
+
+ {csvFile ? ( + <> + + {csvFile.name} + + ) : ( + Ningún archivo seleccionado + )} +
+ +
+
+ )} + +
+ +
+
+ )} + + {/* ── PASO 2: Reglas ── */} + {step === 2 && ( +
+
+
+

Reglas por columna

+

+ Elige una columna, aplica reglas y mira el cambio al instante. + {analysis?.row_count != null ? ` · ${analysis.row_count} filas` : ""} + {previewLoading ? " · actualizando…" : ""} +

+
+ {previewLoading && } +
+ + {loading && !analysis ? ( +
+ Analizando… +
+ ) : analysis ? ( +
+ {/* Lista compacta de columnas */} + + + {/* Detalle de la columna activa */} +
+ {previewError && ( +
+ {previewError} +
+ )} + + {(() => { + const col = analysis.columns.find((c) => c.name === activeColumn) ?? analysis.columns[0]; + if (!col) return

Sin columnas.

; + const applicable = col.applicable_rules ?? []; + const selected = rulesPerColumn[col.name] ?? []; + const beforeFromSamples = displaySamples(col); + const pv = previewColumn(col.name); + const beforeVals = pv?.before?.length ? pv.before.slice(0, 4) : beforeFromSamples.slice(0, 4); + const derived = derivedColumnNames(col.name); + const afterVals = pv?.after?.length + ? pv.after.slice(0, 4) + : selected.length === 0 + ? beforeVals + : []; + const sourceUnchanged = + beforeVals.length > 0 && + afterVals.length > 0 && + beforeVals.every((v, i) => String(v) === String(afterVals[i] ?? "")); + const changed = Boolean(pv?.changed) || derived.length > 0; + + return ( + <> +
+
+

{col.name}

+
+ {col.inferred_type} + {col.null_count != null && ( + nulos {col.null_count}{col.null_pct != null ? ` (${Math.round(col.null_pct)}%)` : ""} + )} + {col.unique_count != null && únicos {col.unique_count}} +
+
+
+ +
+
+
Antes
+
+ {beforeVals.length === 0 ? ( + Sin valores + ) : ( + beforeVals.map((v, i) => ( + {v || "—"} + )) + )} +
+
+ +
+
+ Después +
+
+ {derived.length > 0 ? ( + derived.map((dName) => { + const dPv = previewColumn(dName); + const sample = (dPv?.after ?? []).find((v) => v) ?? (dPv?.after?.[0] ?? ""); + return ( + + {dName.replace(/^.*_doc_/, "_doc_")} + {sample || "—"} + + ); + }) + ) : afterVals.length === 0 ? ( + + {previewLoading ? "…" : "Activa una regla"} + + ) : sourceUnchanged ? ( + Sin cambio en origen + ) : ( + afterVals.map((v, i) => { + const before = beforeVals[i] ?? ""; + const isDiff = String(v) !== String(before); + return ( + + {v || "—"} + + ); + }) + )} +
+
+
+ +
+
Reglas
+ {applicable.length === 0 ? ( + Sin reglas para este tipo + ) : ( +
+ {applicable.map((ruleId) => { + const on = selected.includes(ruleId); + const info = rulesCatalog.find((r) => r.id === ruleId); + return ( + + ); + })} +
+ )} + {selected.length > 0 && ( +

+ {selected + .map((id) => rulesCatalog.find((r) => r.id === id)?.description || id) + .join(" · ")} +

+ )} + {selected.includes("cop_mod_split") && ( +
+ + +

+ Las cuotas por año se editan en Configuración → Limpieza. +

+
+ )} +
+ + ); + })()} + + {/* Una sola tabla de muestra: Antes | Después */} + {((analysis.sample_rows?.length ?? 0) > 0 || (preview?.rows_after.length ?? 0) > 0) && ( +
+
+ Muestra +
+ + +
+
+
+ + + + {orderedSampleColumnNames(sampleMode).map((name) => { + const isNew = !(analysis.columns.some((c) => c.name === name)); + const changed = preview?.columns.find((x) => x.name === name)?.changed; + return ( + + ); + })} + + + + {(sampleMode === "after" && preview + ? preview.rows_after + : analysis.sample_rows ?? preview?.rows_before ?? [] + ).slice(0, 4).map((row, i) => ( + + {orderedSampleColumnNames(sampleMode).map((name) => { + const val = row[name]; + const before = preview?.rows_before[i]?.[name]; + const isNew = !(analysis.columns.some((c) => c.name === name)); + const diff = + sampleMode === "after" && + (isNew || String(before ?? "") !== String(val ?? "")); + return ( + + ); + })} + + ))} + +
+ {name} +
+ {val == null || val === "" + ? — + : String(val).slice(0, 36)} +
+
+
+ )} +
+
+ ) : ( +

No hay análisis. Vuelve al paso Fuente.

+ )} + +
+ + +
+
+ )} + + {/* ── PASO 3: Resultados ── */} + {step === 3 && ( +
+
+

Resultado de la limpieza

+
+ + {resultJob?.status === "completed" ? ( + <> +
+ + + Limpieza completada + {resultJob.log?.length ? ` · ${resultJob.log.length} operaciones` : null} + {resultRows.length > 0 ? ` · ${resultRows.length} filas` : null} + +
+ + {resultJob.log && resultJob.log.length > 0 && ( +
+ Ver log de operaciones +
+
    + {resultJob.log.map((line, i) => ( +
  • {line}
  • + ))} +
+
+
+ )} + +
+ + Vista previa{resultRows.length > 100 ? " (primeras 100)" : ""} + + {resultRows.length > 0 && ( + + )} +
+ + {resultRows.length > 0 ? ( + + ) : ( +

Sin filas en el resultado.

+ )} + + ) : ( +
+ + {resultJob?.error ?? "La limpieza no se completó"} +
+ )} + +
+ + +
+
+ )} +
+
+ ); +} diff --git a/services/desktop_app/src/renderer/modules/cleaning/CleaningWizardModule.tsx b/services/desktop_app/src/renderer/modules/cleaning/CleaningWizardModule.tsx new file mode 100644 index 0000000..41cb367 --- /dev/null +++ b/services/desktop_app/src/renderer/modules/cleaning/CleaningWizardModule.tsx @@ -0,0 +1,26 @@ +import type { BackgroundProgress } from "../../../shared/types"; +import type { ModuleWizardProps } from "../registry"; +import { CleaningWizard } from "./CleaningWizard"; + +interface Props extends ModuleWizardProps { + onProgress?: (p: BackgroundProgress) => void; +} + +export function CleaningWizardModule({ + client, + job, + onTaskCreated, + onProgress, + context, +}: Props) { + return ( + + ); +} diff --git a/services/desktop_app/src/renderer/modules/counting/CountingResults.tsx b/services/desktop_app/src/renderer/modules/counting/CountingResults.tsx index 1e5d848..b0becd7 100644 --- a/services/desktop_app/src/renderer/modules/counting/CountingResults.tsx +++ b/services/desktop_app/src/renderer/modules/counting/CountingResults.tsx @@ -2,6 +2,7 @@ import { ArrowRight, Download, Loader2, RefreshCw } from "lucide-react"; import { useEffect, useState } from "react"; import type { CountJobFile } from "../../../shared/types"; import { CountResultsTable } from "../../components/CountResultsTable"; +import { downloadCsv, toCsv, buildExportCsvFilename } from "../../lib/csv"; import type { ModuleResultsProps } from "../registry"; export function CountingResults({ @@ -47,25 +48,16 @@ export function CountingResults({ : files; const headers = ["filename", "radicado", "service_count", "status", "is_valid", "error"]; - const rows = subset_files.map((f) => - [ - `"${f.filename}"`, - `"${f.radicado ?? ""}"`, - f.service_count ?? "", - f.status, - f.is_valid === null ? "" : String(f.is_valid), - `"${(f.error ?? "").replace(/"/g, '""')}"`, - ].join(","), - ); - const csv = [headers.join(","), ...rows].join("\n"); - const blob = new Blob([csv], { type: "text/csv" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; + const rows = subset_files.map((f) => [ + f.filename, + f.radicado ?? "", + f.service_count ?? "", + f.status, + f.is_valid === null ? "" : String(f.is_valid), + f.error ?? "", + ]); const label = subset === "valid" ? "validos" : subset === "invalid" ? "invalidos" : "todos"; - a.download = `conteo-${label}-${new Date().toISOString().slice(0, 10)}.csv`; - a.click(); - URL.revokeObjectURL(url); + downloadCsv(buildExportCsvFilename("conteo", job.title, label), toCsv(headers, rows)); } const validCount = files.filter(isFileValid).length; diff --git a/services/desktop_app/src/renderer/modules/extraction/ExtractionResults.tsx b/services/desktop_app/src/renderer/modules/extraction/ExtractionResults.tsx index 25e6e26..de07aea 100644 --- a/services/desktop_app/src/renderer/modules/extraction/ExtractionResults.tsx +++ b/services/desktop_app/src/renderer/modules/extraction/ExtractionResults.tsx @@ -1,11 +1,13 @@ -import { Download, Loader2, RefreshCw } from "lucide-react"; +import { ArrowRight, Download, Loader2, RefreshCw, Sparkles } from "lucide-react"; import { useEffect, useState } from "react"; import type { ExtractionBatch } from "../../../shared/types"; +import { ExtractionResultsTable } from "../../components/ExtractionResultsTable"; +import { downloadCsv, toCsv, buildExportCsvFilename } from "../../lib/csv"; import type { ModuleResultsProps } from "../registry"; type Row = Record; -export function ExtractionResults({ client, task }: ModuleResultsProps) { +export function ExtractionResults({ client, task, job, onActivateModule }: ModuleResultsProps) { const [batch, setBatch] = useState(null); const [rows, setRows] = useState([]); const [columns, setColumns] = useState([]); @@ -54,25 +56,24 @@ export function ExtractionResults({ client, task }: ModuleResultsProps) { }, [task.id]); // eslint-disable-line react-hooks/exhaustive-deps function exportCsv() { - const headers = columns; - const csvRows = rows.map((r) => - headers - .map((h) => { - const v = r[h]; - if (v === null || v === undefined) return ""; - const s = String(v); - return `"${s.replace(/"/g, '""')}"`; - }) - .join(","), + const csv = toCsv( + columns, + rows.map((r) => columns.map((h) => r[h])), ); - const csv = [headers.join(","), ...csvRows].join("\n"); - const blob = new Blob([csv], { type: "text/csv" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `extraccion-${new Date().toISOString().slice(0, 10)}.csv`; - a.click(); - URL.revokeObjectURL(url); + downloadCsv(buildExportCsvFilename("extraccion", job.title), csv); + } + + function startCleaning() { + const batchId = String( + (task.result as Record)?.extraction_batch_id + ?? batch?.id + ?? "", + ); + onActivateModule?.("cleaning", { + extractionBatchId: batchId || undefined, + extractionTaskId: task.id, + jobId: job.id, + }); } if (loading && !batch) { @@ -109,15 +110,22 @@ export function ExtractionResults({ client, task }: ModuleResultsProps) { {batch?.status ?? "pendiente"}
- {rows.length > 0 && ( - - )} +
+ {rows.length > 0 && ( + + )} + {rows.length > 0 && !isRunning && onActivateModule && ( + + )} +

{isRunning && ( -
+
Extracción en progreso… @@ -132,32 +140,9 @@ export function ExtractionResults({ client, task }: ModuleResultsProps) {

No se encontraron filas extraídas.

- ) : ( -
- - - - {columns.map((c) => ( - - ))} - - - - {rows.map((row, i) => ( - - {columns.map((c) => ( - - ))} - - ))} - -
{c}
- {row[c] === null || row[c] === undefined - ? — - : String(row[c])} -
-
- )} + ) : rows.length > 0 ? ( + + ) : null}
); } diff --git a/services/desktop_app/src/renderer/modules/extraction/JobExtractionWizard.tsx b/services/desktop_app/src/renderer/modules/extraction/JobExtractionWizard.tsx index 779948b..3a58981 100644 --- a/services/desktop_app/src/renderer/modules/extraction/JobExtractionWizard.tsx +++ b/services/desktop_app/src/renderer/modules/extraction/JobExtractionWizard.tsx @@ -30,6 +30,7 @@ import { defaultAlias } from "../../lib/schemaDisplay"; import { loadApiBaseUrl } from "../../lib/apiDefaults"; import { splitRadicados } from "../../lib/pathMapping"; import type { ModuleWizardProps } from "../registry"; +import { ExtractionResults } from "./ExtractionResults"; declare const desktopApi: import("../../../shared/types").DesktopBridge; @@ -71,6 +72,7 @@ export function JobExtractionWizard({ client, job, onTaskCreated, + onActivateModule, context, countingTaskId: countingTaskIdProp, validCounts: validCountsProp, @@ -95,6 +97,9 @@ export function JobExtractionWizard({ const [extractionStep, setExtractionStep] = useState(() => countingTaskId ? 2 : 1); const [maxReachedStep, setMaxReachedStep] = useState(() => countingTaskId ? 2 : 1); const [taskCreated, setTaskCreated] = useState(null); + /** Rutas de muestras ya usadas en descubrimiento (para no repetir al reintentar). */ + const [usedDiscoveryPaths, setUsedDiscoveryPaths] = useState([]); + const [discoverySampleLabels, setDiscoverySampleLabels] = useState([]); const flattened = useMemo( () => (schemaResponse ? flattenSchema(schemaResponse) : { schema: {}, sample: null }), @@ -151,6 +156,8 @@ export function JobExtractionWizard({ setSchemaResponse(null); setSelection({ fields: [], aliasMap: {} }); setEstimateJson(null); + setUsedDiscoveryPaths([]); + setDiscoverySampleLabels([]); setMaxReachedStep(1); if (extractionStep > 1) setExtractionStep(1); } @@ -167,7 +174,6 @@ export function JobExtractionWizard({ resetSchemaState(); setMaxReachedStep(1); setStatusText(`${indexed.length} documento(s) listos`); - // Actualizar carpeta del job try { await client.updateJob(job.id, { folder_path: selected }); } catch { /* ignore */ } } catch (err) { setManifest([]); @@ -190,41 +196,91 @@ export function JobExtractionWizard({ } } - async function discoverSchema() { - await runTask("Descubriendo campos...", async () => { - if (!window.desktopApi) throw new Error("Requiere Electron."); - if (manifest.length === 0) throw new Error("Selecciona una carpeta con documentos."); + function pickSampleEntry( + candidates: ManifestEntry[], + exclude: Set, + preferRandom: boolean, + ): ManifestEntry { + if (candidates.length === 0) { + throw new Error("No hay documentos candidatos para la muestra."); + } + const unused = candidates.filter((e) => !exclude.has(e.localPath)); + const pool = unused.length > 0 ? unused : candidates; + if (!preferRandom && unused.length === candidates.length) { + return candidates[0]; + } + return pool[Math.floor(Math.random() * pool.length)]!; + } - const responses: SchemaResponse[] = []; - if (form.mode === "general") { - const entry = manifest[0]; - const fileData = await window.desktopApi.readFile(entry.localPath); - const blob = new Blob([fileData.data]); - responses.push(await client.discoverSchemaUpload(buildSchemaUploadForm(form, blob, fileData.filename))); - } else { - for (const docType of validDocTypes(form.docTypes)) { - const entry = manifest.find((item) => item.docType === docType.name); - if (!entry) throw new Error(`No se encontró muestra para ${docType.name}.`); + async function discoverSchema(options?: { randomOther?: boolean }) { + const randomOther = Boolean(options?.randomOther); + await runTask( + randomOther ? "Probando con otro archivo…" : "Descubriendo campos...", + async () => { + if (!window.desktopApi) throw new Error("Requiere Electron."); + if (manifest.length === 0) throw new Error("Selecciona una carpeta con documentos."); + + const exclude = new Set(randomOther ? usedDiscoveryPaths : []); + const responses: SchemaResponse[] = []; + const picked: ManifestEntry[] = []; + + if (form.mode === "general") { + const entry = pickSampleEntry(manifest, exclude, randomOther); + picked.push(entry); const fileData = await window.desktopApi.readFile(entry.localPath); const blob = new Blob([fileData.data]); responses.push( - await client.discoverSchemaUpload(buildSchemaUploadForm(form, blob, fileData.filename, docType.name)), + await client.discoverSchemaUpload( + buildSchemaUploadForm(form, blob, fileData.filename), + ), ); + } else { + for (const docType of validDocTypes(form.docTypes)) { + const typed = manifest.filter((item) => item.docType === docType.name); + if (typed.length === 0) { + throw new Error(`No se encontró muestra para ${docType.name}.`); + } + const entry = pickSampleEntry(typed, exclude, randomOther); + picked.push(entry); + const fileData = await window.desktopApi.readFile(entry.localPath); + const blob = new Blob([fileData.data]); + responses.push( + await client.discoverSchemaUpload( + buildSchemaUploadForm(form, blob, fileData.filename, docType.name), + ), + ); + } } - } - const schema = mergeSchemaResponses(responses); - setSchemaResponse(schema); - const flat = flattenSchema(schema); - setSelection({ - fields: [], - aliasMap: Object.fromEntries(Object.keys(flat.schema).map((p) => [p, defaultAlias(p)])), - }); - setEstimateJson(null); - setStatusText(`${Object.keys(flat.schema).length} campo(s) descubiertos`); - setMaxReachedStep((cur) => (cur < 3 ? 3 : cur)); - setExtractionStep(3); - }); + const schema = mergeSchemaResponses(responses); + setSchemaResponse(schema); + const flat = flattenSchema(schema); + setSelection({ + fields: [], + aliasMap: Object.fromEntries(Object.keys(flat.schema).map((p) => [p, defaultAlias(p)])), + }); + setEstimateJson(null); + const labels = picked.map((e) => e.filename); + setDiscoverySampleLabels(labels); + setUsedDiscoveryPaths((prev) => { + const next = new Set(randomOther ? prev : []); + for (const e of picked) next.add(e.localPath); + return [...next]; + }); + setStatusText( + `${Object.keys(flat.schema).length} campo(s) descubiertos` + + (labels.length ? ` · muestra: ${labels.join(", ")}` : ""), + ); + setMaxReachedStep((cur) => (cur < 3 ? 3 : cur)); + if (!randomOther) { + setExtractionStep(3); + } + }, + ); + } + + async function rediscoverWithOtherSample() { + await discoverSchema({ randomOther: true }); } async function estimate() { @@ -419,14 +475,26 @@ export function JobExtractionWizard({ }); } - // Si ya se creó tarea, muestra mensaje y el módulo Results tomará el relevo + // Tras crear la tarea: mostrar resultados (con polling) en esta misma pestaña if (taskCreated) { return ( -
-

✓ Extracción iniciada

-

- Los resultados aparecerán automáticamente en esta pestaña. -

+
+ {(ingestProgress || busy) && ( +
+ + {statusText || "Extracción en curso…"} +
+ )} + { + setTaskCreated(t); + onTaskCreated(t); + }} + onActivateModule={onActivateModule} + />
); } @@ -461,7 +529,16 @@ export function JobExtractionWizard({ error={error} statusText={statusText} selectRoot={selectRoot} - discoverSchema={discoverSchema} + discoverSchema={() => void discoverSchema()} + rediscoverWithOtherSample={() => void rediscoverWithOtherSample()} + discoverySampleLabels={discoverySampleLabels} + canRediscoverWithOtherSample={ + form.mode === "general" + ? manifest.length > 1 + : validDocTypes(form.docTypes).some( + (dt) => manifest.filter((m) => m.docType === dt.name).length > 1, + ) + } estimate={estimate} runExtraction={runExtraction} selection={selection} diff --git a/services/desktop_app/src/renderer/styles.css b/services/desktop_app/src/renderer/styles.css index ab82bae..c727d36 100644 --- a/services/desktop_app/src/renderer/styles.css +++ b/services/desktop_app/src/renderer/styles.css @@ -73,15 +73,12 @@ button:disabled { } .brand-mark { - align-items: center; - background: linear-gradient(135deg, #2e7db2, #3b9ad9); + background: #0b0b0b; border-radius: 10px; - color: #ffffff; - display: flex; - font-size: 13px; - font-weight: 800; + display: block; + flex-shrink: 0; height: 34px; - justify-content: center; + object-fit: contain; width: 34px; } @@ -289,6 +286,212 @@ button:disabled { flex: 1; } +.settings-page { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; +} + +.settings-page-header { + border-bottom: 1px solid #e8edf4; + flex-shrink: 0; + padding: 20px 28px 16px; +} + +.settings-page-header h2 { + font-size: 18px; + font-weight: 700; + margin: 0 0 6px; +} + +.settings-page-header p { + color: #64748b; + font-size: 13px; + line-height: 1.45; + margin: 0; + max-width: 640px; +} + +.settings-page-header-row { + align-items: flex-start; + display: flex; + gap: 16px; + justify-content: space-between; +} + +.settings-page-actions { + align-items: center; + display: flex; + flex-shrink: 0; + gap: 8px; +} + +.settings-saved-badge { + background: #dcfce7; + border-radius: 999px; + color: #15803d; + font-size: 11px; + font-weight: 600; + padding: 4px 10px; +} + +.settings-page-body { + display: flex; + flex: 1; + flex-direction: column; + gap: 16px; + min-height: 0; + overflow-y: auto; + padding: 20px 28px 28px; +} + +.settings-page-body--fill { + overflow: hidden; +} + +.settings-panel { + background: #ffffff; + border: 1px solid #e3e8ef; + border-radius: 12px; + display: flex; + flex-direction: column; + gap: 14px; + max-width: 720px; + padding: 18px 20px; +} + +.settings-panel-title { + font-size: 14px; + font-weight: 700; + margin: 0; +} + +.copmod-year-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.copmod-year-row { + align-items: center; + display: grid; + gap: 10px; + grid-template-columns: 64px minmax(0, 1fr) auto; +} + +.copmod-year-label { + color: #334155; + font-size: 13px; + font-weight: 700; +} + +.copmod-add-row { + align-items: center; + display: flex; + gap: 10px; + margin-top: 4px; +} + +.copmod-add-year { + max-width: 120px; +} + +.cleaning-copmod-fecha { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 12px; + max-width: 420px; +} + +.settings-status-summary { + color: #15803d; + font-size: 12px; + font-weight: 600; +} + +.settings-status-card--error .settings-status-summary { + color: #b91c1c; +} + +.settings-service-grid { + display: grid; + gap: 10px; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + max-width: 720px; +} + +.settings-service-chip { + align-items: center; + background: #ffffff; + border: 1px solid #e3e8ef; + border-radius: 10px; + display: flex; + gap: 10px; + padding: 12px 14px; +} + +.settings-service-chip--ok { + background: #f0fdf6; + border-color: #bbf7d0; +} + +.settings-service-chip--err { + background: #fff5f5; + border-color: #fecaca; +} + +.settings-service-chip-dot { + background: #94a3b8; + border-radius: 50%; + flex-shrink: 0; + height: 8px; + width: 8px; +} + +.settings-service-chip--ok .settings-service-chip-dot { + background: #16a34a; +} + +.settings-service-chip--err .settings-service-chip-dot { + background: #dc2626; +} + +.settings-service-chip-text { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.settings-service-chip-text strong { + color: #18212f; + font-size: 13px; + font-weight: 700; +} + +.settings-service-chip-text span { + color: #64748b; + font-size: 11.5px; +} + +.settings-url-row { + align-items: stretch; + display: flex; + gap: 10px; +} + +.settings-url-row .form-input { + flex: 1; + min-width: 0; +} + +.settings-url-row .btn { + flex-shrink: 0; +} + .topbar-inline { align-items: center; display: flex; @@ -1520,7 +1723,7 @@ th { align-items: center; display: flex; flex-shrink: 0; - padding: 0 24px 24px; + padding: 20px 24px 24px; } .stepper-item { @@ -1620,6 +1823,15 @@ th { padding: 0 2px 8px; } +.job-extraction-results-pane { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: auto; + padding: 8px 4px 16px; +} + .wiz-step { display: flex; flex-direction: column; @@ -1995,6 +2207,52 @@ th { padding: 10px 16px; } +.wiz-rediscover-card { + align-items: flex-start; + background: #f8fafc; + border: 1px solid #e3e8ef; + border-radius: 10px; + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: space-between; + margin-top: 14px; + max-width: 560px; + padding: 14px 16px; +} + +.wiz-rediscover-card--compact { + margin-bottom: 12px; + margin-top: 0; + max-width: none; +} + +.wiz-rediscover-text { + display: flex; + flex: 1; + flex-direction: column; + gap: 4px; + min-width: 180px; +} + +.wiz-rediscover-text strong { + color: #18212f; + font-size: 13px; + font-weight: 700; +} + +.wiz-rediscover-text span { + color: #64748b; + font-size: 12px; + line-height: 1.45; +} + +.wiz-rediscover-text em { + color: #334155; + font-style: normal; + font-weight: 600; +} + /* ═══════════════════════════════════════════ Review grid (step 4) ═══════════════════════════════════════════ */ @@ -2496,9 +2754,13 @@ th { .count-results { display: flex; + flex: 1 1 auto; flex-direction: column; gap: 16px; margin-top: 12px; + min-height: 0; + overflow: hidden; + padding: 0 16px 16px; } /* Summary stat overrides */ @@ -2626,7 +2888,9 @@ th { .count-table-wrap { border: 1px solid #e8edf4; border-radius: 10px; - overflow: hidden; + flex: 1 1 auto; + min-height: 0; + overflow: auto; } .count-table { @@ -3332,7 +3596,7 @@ th { background: none; border: none; border-radius: 6px; - color: var(--text-muted); + color: #8a9ab4; cursor: pointer; display: flex; font-size: 12.5px; @@ -3344,27 +3608,33 @@ th { } .side-job-back:hover { - background: var(--border); - color: var(--text); + background: rgba(255, 255, 255, 0.05); + color: #c8d5e4; } .side-job-name { - color: var(--text); - font-size: 13px; - font-weight: 700; - margin: 4px 12px 6px; + color: #c8d5e4; + font-size: 12.5px; + font-weight: 600; + line-height: 1.35; + margin: 2px 12px 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .side-section-divider { - background: var(--border); + background: rgba(255, 255, 255, 0.06); + flex-shrink: 0; height: 1px; - margin: 4px 10px; + margin: 8px 10px; } .side-nav--bottom { + display: flex; + flex-direction: column; + flex-shrink: 0; + gap: 2px; margin-top: auto; } @@ -3700,8 +3970,49 @@ th { .task-badge--failed { background: #fee2e2; color: #b91c1c; } /* ── Results table (extracción) ── */ +.extraction-results { + display: flex; + flex: 1 1 auto; + flex-direction: column; + gap: 16px; + min-height: 0; + overflow: hidden; + padding: 12px 16px 16px; +} + +.extraction-table-wrap { + border: 1px solid #e8edf4; + border-radius: 10px; + flex: 1 1 auto; + min-height: 0; + overflow: auto; + padding: 0; +} + +.extraction-empty { + color: var(--text-muted); + padding: 28px 12px !important; + text-align: center; +} + +.extraction-empty-cell { + color: var(--text-muted); +} + +.extraction-row--incomplete td { + background: #fefce8; +} + +.extraction-filter-hint { + color: var(--text-muted); + flex-shrink: 0; + font-size: 12px; + margin: 0; +} + .results-table-wrapper { flex: 1; + min-height: 0; overflow: auto; padding: 0 16px 16px; } @@ -3953,3 +4264,445 @@ th { margin-left: auto; padding: 3px 10px; } + +/* ── Cleaning wizard: lista de extracciones / reglas ── */ +.cleaning-source-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: min(280px, 36vh); + overflow-y: auto; +} + +.cleaning-source-item { + align-items: center; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 10px; + cursor: pointer; + display: flex; + gap: 12px; + justify-content: space-between; + padding: 12px 14px; + text-align: left; + transition: border-color 0.15s, background 0.15s; + width: 100%; +} + +.cleaning-source-item:hover { + border-color: #94a3b8; + background: #f8fafc; +} + +.cleaning-source-item--selected { + border-color: #2563eb; + background: #eff6ff; + box-shadow: 0 0 0 1px #2563eb; +} + +.cleaning-source-item-main { + align-items: flex-start; + display: flex; + gap: 10px; + min-width: 0; +} + +.cleaning-source-item-title { + color: #0f172a; + font-size: 13px; + font-weight: 600; +} + +.cleaning-source-item-meta { + color: #64748b; + font-size: 12px; + margin-top: 2px; +} + +/* ── Paso Reglas: lista fija + detalle con scroll ── */ +.wiz-step--cleaning-rules { + display: flex; + flex: 1; + flex-direction: column; + gap: 12px; + min-height: 0; +} + +.cleaning-rules-toolbar { + align-items: flex-start; + display: flex; + flex-shrink: 0; + gap: 12px; + justify-content: space-between; +} + +.cleaning-rules-workspace { + display: grid; + flex: 1; + gap: 12px; + grid-template-columns: minmax(160px, 220px) minmax(0, 1fr); + min-height: 0; + overflow: hidden; +} + +.cleaning-col-nav { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 10px; + display: flex; + flex-direction: column; + gap: 2px; + min-height: 0; + overflow-y: auto; + padding: 6px; +} + +.cleaning-col-nav-item { + align-items: center; + background: transparent; + border: none; + border-radius: 8px; + color: #334155; + cursor: pointer; + display: flex; + gap: 8px; + justify-content: space-between; + padding: 8px 10px; + text-align: left; + width: 100%; +} + +.cleaning-col-nav-item:hover { + background: #eef2f7; +} + +.cleaning-col-nav-item--active { + background: #fff; + box-shadow: 0 0 0 1px #2563eb; + color: #0f172a; +} + +.cleaning-col-nav-item--changed:not(.cleaning-col-nav-item--active) { + background: #eff6ff; +} + +.cleaning-col-nav-name { + font-size: 12px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cleaning-col-nav-meta { + align-items: center; + display: flex; + flex-shrink: 0; + gap: 4px; +} + +.cleaning-col-nav-count { + background: #2563eb; + border-radius: 999px; + color: #fff; + font-size: 10px; + font-weight: 700; + min-width: 18px; + padding: 1px 6px; + text-align: center; +} + +.cleaning-col-detail { + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 10px; + display: flex; + flex-direction: column; + gap: 12px; + min-height: 0; + min-width: 0; + overflow-x: hidden; + overflow-y: auto; + padding: 12px 14px; +} + +.cleaning-col-detail-header, +.cleaning-preview-compare--compact, +.cleaning-rules-block, +.cleaning-copmod-fecha { + flex-shrink: 0; +} + +.cleaning-col-detail-title { + font-size: 15px; + font-weight: 700; + margin: 0; +} + +.cleaning-rules-block { + display: flex; + flex-direction: column; + gap: 6px; +} + +.cleaning-rule-desc { + margin: 0; +} + +.cleaning-preview-compare--compact { + background: #f8fafc; + border: 1px solid #e8edf4; + border-radius: 8px; + margin: 0; + padding: 10px; +} + +.cleaning-preview-compare--changed { + background: #f0f7ff; + border-color: #bfdbfe; +} + +.cleaning-sample-dock { + border-top: 1px solid #e8edf4; + display: flex; + flex: 0 0 auto; + flex-direction: column; + gap: 8px; + min-height: 140px; + padding-top: 10px; +} + +.cleaning-sample-dock-header { + align-items: center; + display: flex; + flex-shrink: 0; + gap: 12px; + justify-content: space-between; +} + +.cleaning-sample-tabs { + background: #eef2f7; + border-radius: 8px; + display: inline-flex; + padding: 2px; +} + +.cleaning-sample-tab { + background: transparent; + border: none; + border-radius: 6px; + color: #64748b; + cursor: pointer; + font-size: 12px; + font-weight: 600; + padding: 4px 10px; +} + +.cleaning-sample-tab:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.cleaning-sample-tab--active { + background: #fff; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); + color: #0f172a; +} + +.cleaning-sample-table-scroll { + max-height: 220px; + min-height: 0; + overflow: auto; +} + +.cleaning-rules-nav { + flex-shrink: 0; + gap: 8px; + margin-top: 0; +} + +.cleaning-type-badge { + background: #e2e8f0; + border-radius: 999px; + color: #334155; + font-size: 10px; + font-weight: 600; + padding: 2px 7px; +} + +.cleaning-rule-card-meta { + align-items: center; + color: #64748b; + display: flex; + flex-wrap: wrap; + font-size: 12px; + gap: 8px; + margin-top: 4px; +} + +.cleaning-rule-chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.cleaning-preview-compare { + align-items: center; + display: grid; + gap: 8px; + grid-template-columns: 1fr auto 1fr; +} + +.cleaning-preview-side { + min-width: 0; +} + +.cleaning-preview-label { + color: #64748b; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.02em; + margin-bottom: 6px; + text-transform: uppercase; +} + +.cleaning-preview-label--after { + color: #1d4ed8; +} + +.cleaning-preview-arrow { + color: #94a3b8; + flex-shrink: 0; +} + +.cleaning-preview-values { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.cleaning-preview-chip { + background: #f1f5f9; + border: 1px solid #e2e8f0; + border-radius: 6px; + color: #0f172a; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 11px; + max-width: 100%; + overflow: hidden; + padding: 3px 7px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cleaning-preview-chip--changed { + background: #dbeafe; + border-color: #93c5fd; + color: #1e3a8a; + font-weight: 600; +} + +.cleaning-preview-chip-col { + color: #64748b; + font-weight: 500; + margin-right: 6px; +} + +.cleaning-preview-empty { + color: #94a3b8; + font-size: 12px; +} + +.cleaning-preview-table { + font-size: 12px; +} + +.cleaning-th-changed, +.cleaning-td-changed { + background: #eff6ff; +} + +@media (max-width: 900px) { + .cleaning-rules-workspace { + grid-template-columns: 1fr; + overflow: auto; + } + + .cleaning-col-nav { + display: grid; + grid-auto-flow: column; + grid-auto-columns: max-content; + max-height: none; + overflow-x: auto; + overflow-y: hidden; + } +} + +/* ── Auto-update modal ── */ +.update-modal-backdrop { + align-items: center; + background: rgba(15, 23, 42, 0.45); + display: flex; + inset: 0; + justify-content: center; + position: fixed; + z-index: 1000; +} + +.update-modal { + background: #ffffff; + border-radius: 12px; + box-shadow: 0 20px 50px rgba(15, 23, 42, 0.25); + display: flex; + flex-direction: column; + gap: 12px; + max-width: 440px; + padding: 20px 22px; + width: calc(100% - 32px); +} + +.update-modal-header { + align-items: center; + display: flex; + justify-content: space-between; + gap: 12px; +} + +.update-modal-header h2 { + font-size: 16px; + font-weight: 700; + margin: 0; +} + +.update-modal-body { + color: #334155; + font-size: 13.5px; + line-height: 1.5; + margin: 0; +} + +.update-modal-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 8px; +} + +.update-progress { + display: flex; + flex-direction: column; + gap: 6px; +} + +.update-progress-bar { + background: #e2e8f0; + border-radius: 999px; + height: 8px; + overflow: hidden; +} + +.update-progress-fill { + background: #2563eb; + height: 100%; + transition: width 0.2s ease; +} diff --git a/services/desktop_app/src/renderer/types/jobSection.ts b/services/desktop_app/src/renderer/types/jobSection.ts index fb2469b..c37a28a 100644 --- a/services/desktop_app/src/renderer/types/jobSection.ts +++ b/services/desktop_app/src/renderer/types/jobSection.ts @@ -1 +1 @@ -export type JobSection = "counting" | "extraction" | "results"; +export type JobSection = "counting" | "extraction" | "cleaning" | "results"; diff --git a/services/desktop_app/src/renderer/views/JobResultsView.tsx b/services/desktop_app/src/renderer/views/JobResultsView.tsx index e4e3eed..7a2dd16 100644 --- a/services/desktop_app/src/renderer/views/JobResultsView.tsx +++ b/services/desktop_app/src/renderer/views/JobResultsView.tsx @@ -8,14 +8,17 @@ import { Loader2, Plus, RefreshCw, + Sparkles, } from "lucide-react"; import { useEffect, useState } from "react"; -import type { BackgroundProgress, CountJobFile, Job, Task } from "../../shared/types"; +import type { BackgroundProgress, CleaningJob, CountJobFile, Job, Task } from "../../shared/types"; import { CountResultsTable } from "../components/CountResultsTable"; +import { ExtractionResultsTable } from "../components/ExtractionResultsTable"; import { BackgroundProgressBanner } from "../components/BackgroundProgressBanner"; import type { ApiClient } from "../lib/apiClient"; +import { downloadCsv, toCsv, buildExportCsvFilename } from "../lib/csv"; -type ResultTab = "counting" | "extraction"; +type ResultTab = "counting" | "extraction" | "cleaning"; interface Props { client: ApiClient; @@ -23,9 +26,11 @@ interface Props { tasks: Task[]; countingProgress?: BackgroundProgress | null; extractionProgress?: BackgroundProgress | null; + cleaningProgress?: BackgroundProgress | null; onRefresh: () => Promise; onStartCounting: () => void; onStartExtraction: (ctx?: Record) => void; + onStartCleaning: (ctx?: Record) => void; } export function JobResultsView({ @@ -34,9 +39,11 @@ export function JobResultsView({ tasks, countingProgress, extractionProgress, + cleaningProgress, onRefresh, onStartCounting, onStartExtraction, + onStartCleaning, }: Props) { const [activeTab, setActiveTab] = useState("counting"); const [refreshing, setRefreshing] = useState(false); @@ -45,6 +52,8 @@ export function JobResultsView({ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); const extractionTasks = tasks.filter((t) => t.type === "extraction") .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + const cleaningTasks = tasks.filter((t) => t.type === "cleaning") + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); async function refresh() { setRefreshing(true); @@ -54,7 +63,7 @@ export function JobResultsView({ return (
- {/* ── Sub-tabs: Conteo | Extracción ── */} + {/* ── Sub-tabs: Conteo | Extracción | Limpieza ── */}
+
@@ -129,12 +165,14 @@ export function JobResultsView({ ══════════════════════════════════════════ */ function CountingResultsSection({ client, + job, tasks, liveProgress, onStartCounting, onExtractFromCount, }: { client: ApiClient; + job: Job; tasks: Task[]; liveProgress?: BackgroundProgress | null; onStartCounting: () => void; @@ -175,17 +213,15 @@ function CountingResultsSection({ function exportCsv(subset: "valid" | "invalid") { const subset_files = subset === "valid" ? files.filter(isFileValid) : files.filter((f) => !isFileValid(f)); const headers = ["filename", "radicado", "service_count", "status", "is_valid", "error"]; - const rows = subset_files.map((f) => - [`"${f.filename}"`, `"${f.radicado ?? ""}"`, f.service_count ?? "", f.status, f.is_valid === null ? "" : String(f.is_valid), `"${(f.error ?? "").replace(/"/g, '""')}"`].join(","), - ); - const csv = [headers.join(","), ...rows].join("\n"); - const blob = new Blob([csv], { type: "text/csv" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `conteo-${subset}-${new Date().toISOString().slice(0, 10)}.csv`; - a.click(); - URL.revokeObjectURL(url); + const rows = subset_files.map((f) => [ + f.filename, + f.radicado ?? "", + f.service_count ?? "", + f.status, + f.is_valid === null ? "" : String(f.is_valid), + f.error ?? "", + ]); + downloadCsv(buildExportCsvFilename("conteo", job.title, subset), toCsv(headers, rows)); } if (tasks.length === 0 && !liveProgress?.running) { @@ -291,14 +327,18 @@ type Row = Record; function ExtractionResultsSection({ client, + job, tasks, liveProgress, onStartExtraction, + onStartCleaning, }: { client: ApiClient; + job: Job; tasks: Task[]; liveProgress?: BackgroundProgress | null; onStartExtraction: () => void; + onStartCleaning: (ctx?: Record) => void; }) { const [selectedTask, setSelectedTask] = useState(tasks[0] ?? null); const [rows, setRows] = useState([]); @@ -344,20 +384,11 @@ function ExtractionResultsSection({ }, [selectedTask?.id]); // eslint-disable-line react-hooks/exhaustive-deps function exportCsv() { - const csv = [columns.join(","), ...rows.map((r) => - columns.map((c) => { - const v = r[c]; - if (v === null || v === undefined) return ""; - return `"${String(v).replace(/"/g, '""')}"`; - }).join(","), - )].join("\n"); - const blob = new Blob([csv], { type: "text/csv" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = `extraccion-${new Date().toISOString().slice(0, 10)}.csv`; - a.click(); - URL.revokeObjectURL(url); + const csv = toCsv( + columns, + rows.map((r) => columns.map((c) => r[c])), + ); + downloadCsv(buildExportCsvFilename("extraccion", job.title), csv); } if (tasks.length === 0 && !liveProgress?.running) { @@ -422,6 +453,24 @@ function ExtractionResultsSection({ CSV ({rows.length} filas) )} + {rows.length > 0 && selectedTask?.status === "completed" && ( + + )} @@ -468,26 +517,162 @@ function ExtractionResultsSection({

No hay filas extraídas.

) : ( -
- - - {columns.map((c) => )} - - - {rows.map((row, i) => ( - - {columns.map((c) => ( - - ))} - + + )} + + ); +} + +/* ══════════════════════════════════════════ + Sección de resultados de limpieza +══════════════════════════════════════════ */ +function CleaningResultsSection({ + client, + job, + tasks, + liveProgress, + onStartCleaning, +}: { + client: ApiClient; + job: Job; + tasks: Task[]; + liveProgress?: BackgroundProgress | null; + onStartCleaning: () => void; +}) { + const [selectedTask, setSelectedTask] = useState(tasks[0] ?? null); + const [cleaningJob, setCleaningJob] = useState(null); + const [rows, setRows] = useState([]); + const [columns, setColumns] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + setSelectedTask(tasks[0] ?? null); + }, [tasks.map((t) => t.id).join(",")]); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + if (!selectedTask) { + setRows([]); + setColumns([]); + setCleaningJob(null); + return; + } + let cancelled = false; + async function load() { + setLoading(true); + setError(null); + try { + const cleaningJobId = String( + (selectedTask!.result as Record)?.cleaning_job_id ?? "", + ); + if (!cleaningJobId) { + setError("La tarea no tiene cleaning_job_id"); + return; + } + const jobData = await client.cleaningJob(cleaningJobId); + if (cancelled) return; + setCleaningJob(jobData); + if (jobData.status === "completed") { + const resp = await client.cleaningJobRows(cleaningJobId, 500, 0); + if (cancelled) return; + const data = resp.rows ?? []; + setRows(data); + setColumns(Array.from(new Set(data.flatMap((r) => Object.keys(r))))); + } + } catch (e) { + if (!cancelled) setError(String(e instanceof Error ? e.message : e)); + } finally { + if (!cancelled) setLoading(false); + } + } + void load(); + return () => { cancelled = true; }; + }, [selectedTask?.id, client]); // eslint-disable-line react-hooks/exhaustive-deps + + function exportCsv() { + const csv = toCsv( + columns, + rows.map((r) => columns.map((h) => r[h])), + ); + downloadCsv(buildExportCsvFilename("limpieza", job.title), csv); + } + + if (tasks.length === 0 && !liveProgress?.running) { + return ( +
+ +

No hay resultados de limpieza todavía.

+ +
+ ); + } + + if (tasks.length === 0 && liveProgress?.running) { + return ( +
+ +
+ ); + } + + return ( +
+ {liveProgress?.running && ( +
+ +
+ )} +
+
+ {tasks.length > 1 && ( +
-
{c}
- {row[c] === null || row[c] === undefined - ? — - : String(row[c])} -
+ + )} + {selectedTask && ( + + {cleaningJob?.status ?? selectedTask.status} + + )} +
+ +
+ {rows.length > 0 && ( + + )} + +
+
+ + {error && ( +
+ {error} +
+ )} + + {loading && rows.length === 0 ? ( +
+ ) : rows.length === 0 ? ( +
+

No hay filas limpiadas.

+ ) : ( + )}
); diff --git a/services/desktop_app/src/renderer/views/JobView.tsx b/services/desktop_app/src/renderer/views/JobView.tsx index a1072fc..75b4f26 100644 --- a/services/desktop_app/src/renderer/views/JobView.tsx +++ b/services/desktop_app/src/renderer/views/JobView.tsx @@ -5,6 +5,7 @@ import type { BackgroundProgress, Job, Task } from "../../shared/types"; import type { JobSection } from "../types/jobSection"; import { BackgroundProgressBanner } from "../components/BackgroundProgressBanner"; import { CountingWizardModule } from "../modules/counting/CountingWizardModule"; +import { CleaningWizardModule } from "../modules/cleaning/CleaningWizardModule"; import { JobExtractionWizard } from "../modules/extraction/JobExtractionWizard"; import { JobResultsView } from "./JobResultsView"; @@ -24,10 +25,13 @@ export function JobView({ client, job, section, sectionContext, onActivateSectio const [savingTitle, setSavingTitle] = useState(false); const [countingProgress, setCountingProgress] = useState(null); const [extractionProgress, setExtractionProgress] = useState(null); + const [cleaningProgress, setCleaningProgress] = useState(null); const pollingRef = useRef | null>(null); const backgroundRunning = - countingProgress?.running === true || extractionProgress?.running === true; + countingProgress?.running === true || + extractionProgress?.running === true || + cleaningProgress?.running === true; const loadTasks = useCallback(async () => { try { @@ -74,6 +78,7 @@ export function JobView({ client, job, section, sectionContext, onActivateSectio const activeBackground = countingProgress?.running ? countingProgress : extractionProgress?.running ? extractionProgress + : cleaningProgress?.running ? cleaningProgress : null; return ( @@ -114,6 +119,7 @@ export function JobView({ client, job, section, sectionContext, onActivateSectio
{section === "counting" && "Conteo de servicios"} {section === "extraction" && "Extracción de datos"} + {section === "cleaning" && "Limpieza de datos"} {section === "results" && "Resultados del trabajo"}
@@ -161,6 +167,18 @@ export function JobView({ client, job, section, sectionContext, onActivateSectio />
+
+ setCleaningProgress(p.running ? p : null)} + onActivateModule={(type, ctx) => onActivateSection(type as JobSection, ctx)} + context={sectionContext} + /> +
+
onActivateSection("counting")} onStartExtraction={(ctx?: Record) => onActivateSection("extraction", ctx)} + onStartCleaning={(ctx?: Record) => onActivateSection("cleaning", ctx)} />
diff --git a/services/desktop_app/src/renderer/views/SettingsView.tsx b/services/desktop_app/src/renderer/views/SettingsView.tsx new file mode 100644 index 0000000..251c3c4 --- /dev/null +++ b/services/desktop_app/src/renderer/views/SettingsView.tsx @@ -0,0 +1,471 @@ +import { + CheckCircle2, + Database, + Loader2, + Plus, + RotateCcw, + Server, + Trash2, + XCircle, +} from "lucide-react"; +import { useEffect, useState } from "react"; +import { DEFAULT_API_BASE_URL } from "../lib/apiDefaults"; +import { + type CopModConfig, + defaultCopModConfig, + loadCopModConfig, + saveCopModConfig, +} from "../lib/cleaningSettings"; + +export type SettingsSection = "connection" | "cleaning" | "backend"; +type Status = "idle" | "ok" | "error" | "busy"; +type BackendTab = "config" | "models"; + +type SettingsViewProps = { + section: SettingsSection; + apiBaseUrl: string; + onApiBaseUrlChange: (url: string) => void; + connectionStatus: Status; + connectionMessage: string; + healthJson?: unknown; + verifyConnection: () => Promise; + busy: boolean; + configJson: unknown; + modelsJson: unknown; +}; + +const SERVICE_LABELS: Record = { + extraction: "Extracción", + cleaning: "Limpieza", + counting: "Conteo", +}; + +type ServiceRow = { id: string; label: string; ok: boolean; detail: string }; + +function parseHealthServices(health: unknown): ServiceRow[] { + if (!health || typeof health !== "object") return []; + const services = (health as { services?: unknown }).services; + if (!services || typeof services !== "object") return []; + return Object.entries(services as Record).map(([id, raw]) => { + const info = raw && typeof raw === "object" ? (raw as Record) : {}; + const ok = Boolean(info.ok); + const code = info.status_code != null ? String(info.status_code) : ""; + const err = typeof info.error === "string" ? info.error : ""; + return { + id, + label: SERVICE_LABELS[id] ?? id, + ok, + detail: ok ? (code ? `HTTP ${code}` : "OK") : err || (code ? `HTTP ${code}` : "Error"), + }; + }); +} + +export function SettingsView({ + section, + apiBaseUrl, + onApiBaseUrlChange, + connectionStatus, + connectionMessage, + healthJson, + verifyConnection, + busy, + configJson, + modelsJson, +}: SettingsViewProps) { + if (section === "connection") { + return ( + + ); + } + if (section === "cleaning") { + return ; + } + return ( + + ); +} + +function ConnectionSettings({ + apiBaseUrl, + onApiBaseUrlChange, + connectionStatus, + connectionMessage, + healthJson, + verifyConnection, + busy, +}: { + apiBaseUrl: string; + onApiBaseUrlChange: (url: string) => void; + connectionStatus: Status; + connectionMessage: string; + healthJson?: unknown; + verifyConnection: () => Promise; + busy: boolean; +}) { + const connectionLabel = + connectionStatus === "ok" + ? "Conectado" + : connectionStatus === "busy" + ? "Verificando…" + : connectionStatus === "error" + ? "Sin conexión" + : "Sin verificar"; + + const services = parseHealthServices(healthJson); + const allOk = services.length > 0 && services.every((s) => s.ok); + + return ( +
+
+

Conexión

+

URL del API Gateway y estado de los servicios del backend.

+
+ +
+
+
+ {connectionStatus === "ok" ? ( + + ) : connectionStatus === "error" ? ( + + ) : connectionStatus === "busy" ? ( + + ) : ( + + )} +
+
+ {connectionLabel} + {connectionMessage} + {connectionStatus === "ok" && services.length > 0 && ( + + {allOk + ? `${services.length} servicios operativos` + : `${services.filter((s) => s.ok).length} de ${services.length} servicios OK`} + + )} +
+
+ + {services.length > 0 && connectionStatus === "ok" && ( +
+ {services.map((svc) => ( +
+ +
+ {svc.label} + {svc.detail} +
+
+ ))} +
+ )} + +
+
+ +
+ onApiBaseUrlChange(e.target.value)} + placeholder={DEFAULT_API_BASE_URL} + spellCheck={false} + /> + +
+

Los cambios se guardan automáticamente en este equipo.

+
+
+
+
+ ); +} + +function parseAmountList(text: string): number[] { + return [ + ...new Set( + text + .split(/[,;\s]+/) + .map((s) => s.trim()) + .filter(Boolean) + .map((s) => Math.round(Number(s.replace(/\./g, "").replace(",", ".")))) + .filter((n) => Number.isFinite(n) && n >= 0), + ), + ].sort((a, b) => a - b); +} + +function CleaningSettings() { + const [config, setConfig] = useState(() => loadCopModConfig()); + const [drafts, setDrafts] = useState>(() => { + const initial = loadCopModConfig(); + return Object.fromEntries( + Object.entries(initial.moderadora_by_year).map(([y, vals]) => [y, vals.join(", ")]), + ); + }); + const [savedFlash, setSavedFlash] = useState(false); + const [newYear, setNewYear] = useState(""); + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + setHydrated(true); + }, []); + + useEffect(() => { + if (!hydrated) return; + saveCopModConfig(config); + setSavedFlash(true); + const t = window.setTimeout(() => setSavedFlash(false), 1200); + return () => window.clearTimeout(t); + }, [config, hydrated]); + + const years = Object.keys(config.moderadora_by_year).sort( + (a, b) => Number(a) - Number(b), + ); + + function commitYear(year: string, text: string) { + const nums = parseAmountList(text); + setDrafts((prev) => ({ ...prev, [year]: nums.join(", ") })); + setConfig((prev) => ({ + moderadora_by_year: { + ...prev.moderadora_by_year, + [year]: nums, + }, + })); + } + + function removeYear(year: string) { + setConfig((prev) => { + const next = { ...prev.moderadora_by_year }; + delete next[year]; + return { moderadora_by_year: next }; + }); + setDrafts((prev) => { + const next = { ...prev }; + delete next[year]; + return next; + }); + } + + function addYear() { + const y = String(Number.parseInt(newYear, 10)); + if (!Number.isFinite(Number(y)) || y === "NaN") return; + if (config.moderadora_by_year[y]) { + setNewYear(""); + return; + } + setConfig((prev) => ({ + moderadora_by_year: { ...prev.moderadora_by_year, [y]: [] }, + })); + setDrafts((prev) => ({ ...prev, [y]: "" })); + setNewYear(""); + } + + function resetDefaults() { + const defaults = defaultCopModConfig(); + setConfig(defaults); + setDrafts( + Object.fromEntries( + Object.entries(defaults.moderadora_by_year).map(([y, vals]) => [y, vals.join(", ")]), + ), + ); + } + + return ( +
+
+
+
+

Limpieza · COP / MOD

+

+ Cuotas moderadora por año. La regla «Separa COP/MOD» clasifica el importe + según el año de la fecha de servicio que elijas en el wizard. +

+
+
+ {savedFlash && Guardado} + +
+
+
+ +
+
+

Cuotas moderadora por año

+

+ Separa los valores con coma o espacio. Si el importe coincide con uno de + estos montos en el año de la fecha, se marca como moderadora; si no, como + copago. +

+ +
+ {years.length === 0 && ( +

No hay años configurados. Añade uno o restaura los defaults.

+ )} + {years.map((year) => ( +
+ + + setDrafts((prev) => ({ ...prev, [year]: e.target.value })) + } + onBlur={(e) => commitYear(year, e.target.value)} + placeholder="ej. 5000, 20100, 52800" + /> + +
+ ))} +
+ +
+ setNewYear(e.target.value)} + placeholder="Año" + inputMode="numeric" + aria-label="Nuevo año" + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + addYear(); + } + }} + /> + +
+
+
+
+ ); +} + +function BackendSettings({ + connectionStatus, + configJson, + modelsJson, + verifyConnection, + busy, +}: { + connectionStatus: Status; + configJson: unknown; + modelsJson: unknown; + verifyConnection: () => Promise; + busy: boolean; +}) { + const [backendTab, setBackendTab] = useState("config"); + const backendData = backendTab === "config" ? configJson : modelsJson; + + return ( +
+
+
+
+

Backend

+

Respuesta en vivo de /config y /models del gateway.

+
+ +
+
+ +
+
+
+ +

Datos del servidor

+
+
+ + +
+
+ +
+ {backendData != null ? ( +
{JSON.stringify(backendData, null, 2)}
+ ) : ( +
+ +

Sin datos del backend

+ + {connectionStatus === "ok" + ? "Pulsa Actualizar para cargar los datos." + : "Verifica la conexión en la sección Conexión."} + +
+ )} +
+
+
+ ); +} diff --git a/services/desktop_app/src/shared/types.ts b/services/desktop_app/src/shared/types.ts index bbc3d67..5873b12 100644 --- a/services/desktop_app/src/shared/types.ts +++ b/services/desktop_app/src/shared/types.ts @@ -164,6 +164,83 @@ export interface CountSyncResponse { failed: number; } +export interface CleaningRuleInfo { + id: string; + name: string; + description: string; +} + +export interface CleaningColumnAnalysis { + name: string; + inferred_type: string; + example?: string | null; + samples?: string[]; + null_count?: number; + null_pct?: number; + unique_count?: number; + applicable_rules?: string[]; + [key: string]: unknown; +} + +export interface CleaningAnalysisResponse { + batch_id?: string; + filename?: string; + delimiter?: string; + row_count?: number; + columns: CleaningColumnAnalysis[]; + sample_rows?: Array>; +} + +export interface CleaningPreviewColumn { + name: string; + before: string[]; + after: string[]; + changed?: boolean; +} + +export interface CleaningPreviewResponse { + rows_before: Array>; + rows_after: Array>; + columns: CleaningPreviewColumn[]; +} + +export interface CleaningPreviewPayload { + rows?: Array>; + extraction_batch_id?: string; + rules_per_column: Record; + cop_mod_fecha_column?: Record; + cop_mod_config?: Record | null; + sample_size?: number; +} + +export interface CleaningJob { + id: string; + extraction_batch_id: string | null; + job_id: string | null; + source: "extraction" | "csv" | string; + status: "pending" | "running" | "completed" | "failed" | string; + request: Record; + log: string[]; + error: string | null; + created_at: string; + updated_at: string; +} + +export interface RunCleaningPayload { + extraction_batch_id: string; + job_id?: string; + rules_per_column: Record; + cop_mod_fecha_column?: Record; + cop_mod_config?: Record | null; +} + +export interface CleaningRowsResponse { + job_id: string; + rows: Array>; + limit: number; + offset: number; +} + export interface CountingFormState { localRoot: string; containerRoot: string; @@ -174,7 +251,7 @@ export interface CountingFormState { /** Estado de progreso de un módulo ejecutándose en segundo plano. */ export interface BackgroundProgress { - module: "counting" | "extraction"; + module: "counting" | "extraction" | "cleaning"; total: number; processed: number; failed: number; @@ -189,6 +266,31 @@ export interface RuntimeConfig { documentRootContainer: string; } +export interface UpdateManifest { + version: string; + releaseDate?: string; + /** Fecha de lanzamiento como en DATA.txt, ej. "06/07/2026" */ + releaseDateDisplay?: string; + fileName: string; + url: string; + sha256: string; + size?: number; +} + +export interface UpdateCheckResult { + updateAvailable: boolean; + currentVersion: string; + manifest?: UpdateManifest; + error?: string; +} + +export interface UpdateProgress { + phase: "downloading" | "verifying" | "installing" | string; + received?: number; + total?: number; + percent?: number; +} + export interface DesktopBridge { getRuntimeConfig(): Promise; selectDirectory(): Promise; @@ -202,4 +304,7 @@ export interface DesktopBridge { keywords: string[], ): Promise; readFile(filePath: string): Promise; + checkForUpdates(): Promise; + installUpdate(manifest: UpdateManifest): Promise<{ ok: boolean; destPath?: string }>; + onUpdateProgress(callback: (progress: UpdateProgress) => void): () => void; } diff --git a/services/extraction/api/main.py b/services/extraction/api/main.py index 7395b7f..5d3eb7c 100644 --- a/services/extraction/api/main.py +++ b/services/extraction/api/main.py @@ -1126,8 +1126,7 @@ def create_count_job(request: CreateCountJobRequest) -> dict[str, Any]: job = session.get(Job, request.job_id) if job is None: raise HTTPException(status_code=404, detail="Job no encontrado") - if request.title: - job.title = request.title + # No sobrescribir el nombre que el usuario dio al trabajo. job.status = "running" job.updated_at = utcnow() else: diff --git a/services/extraction/infrastructure/database.py b/services/extraction/infrastructure/database.py index f521832..dfbc08a 100644 --- a/services/extraction/infrastructure/database.py +++ b/services/extraction/infrastructure/database.py @@ -136,7 +136,13 @@ class CleaningJob(Base): __tablename__ = "cleaning_jobs" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id) - extraction_batch_id: Mapped[str] = mapped_column(ForeignKey("extraction_batches.id"), index=True) + extraction_batch_id: Mapped[str | None] = mapped_column( + ForeignKey("extraction_batches.id"), nullable=True, index=True + ) + job_id: Mapped[str | None] = mapped_column( + String(36), ForeignKey("jobs.id"), nullable=True, index=True + ) + source: Mapped[str] = mapped_column(String(32), default="extraction", index=True) status: Mapped[str] = mapped_column(String(32), default="pending", index=True) request: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) log: Mapped[list[str]] = mapped_column(JSON, default=list) @@ -150,7 +156,9 @@ class CleanedRow(Base): id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) cleaning_job_id: Mapped[str] = mapped_column(ForeignKey("cleaning_jobs.id"), index=True) - extraction_batch_id: Mapped[str] = mapped_column(ForeignKey("extraction_batches.id"), index=True) + extraction_batch_id: Mapped[str | None] = mapped_column( + ForeignKey("extraction_batches.id"), nullable=True, index=True + ) row_index: Mapped[int] = mapped_column(Integer, default=0) row_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)