feat: Implement moderadora preferences management and enhance CSV cleaning rules for COP/MOD separation. Introduce new GUI components for moderadora settings, including default values and user input handling. Update application preferences to support new extraction models and improve overall user experience in data processing.

This commit is contained in:
erik
2026-05-13 01:55:32 -05:00
parent e2536380d4
commit 6ee6df5469
17 changed files with 1031 additions and 54 deletions
+83
View File
@@ -69,6 +69,89 @@ def _get_at_path(obj: Any, path: str) -> Any:
return cur
def set_at_path(root: Dict[str, Any], path: str, value: Any) -> None:
"""Asigna ``value`` en ``root`` siguiendo la ruta con segmentos ``/`` (crea dicts intermedios)."""
parts = [p for p in path.split('/') if p]
if not parts:
return
cur: Any = root
for p in parts[:-1]:
if not isinstance(cur, dict):
return
nxt = cur.get(p)
if not isinstance(nxt, dict):
cur[p] = {}
nxt = cur[p]
cur = nxt
if isinstance(cur, dict):
cur[parts[-1]] = value
def _resolve_raw_string_for_scalar_path(raw: Dict[str, Any], path: str) -> tuple[Optional[str], bool]:
"""Resuelve un string en ``raw`` para la ruta canónica ``path``.
Devuelve ``(valor, ambiguous)``. Si ``ambiguous`` es True, no debe usarse ningún valor
(varias claves aplanadas comparten el último segmento del path).
"""
direct = _get_at_path(raw, path)
if isinstance(direct, str) and direct.strip():
return direct, False
parts = [p for p in path.split('/') if p]
if not parts:
return None, False
last = parts[-1]
raw_flat = flatten_non_list_fields(raw)
same_last: List[tuple[str, str]] = []
for k, v in raw_flat.items():
if not isinstance(v, str) or not v.strip():
continue
segs = [s for s in k.split('/') if s]
if not segs:
continue
if segs[-1] == last:
same_last.append((k, v))
if len(same_last) == 1:
return same_last[0][1], False
if len(same_last) > 1:
return None, True
return None, False
def merge_verbatim_long_strings(
raw: Dict[str, Any],
normalized: Dict[str, Any],
paths: Optional[List[str]],
) -> Dict[str, Any]:
"""Sustituye en ``normalized`` cadenas truncadas por la versión más larga del ``raw`` (misma ruta o sufijo único).
Solo actúa sobre rutas escalares (sin ``*``). Evita sustituciones cuando hay varias claves
candidatas con el mismo último segmento o cuando el texto normalizado no parece un prefijo
del crudo (salvo cadena vacía o solo espacios).
"""
if not isinstance(normalized, dict) or not isinstance(raw, dict):
return normalized
for path in paths or []:
if not path or '*' in path:
continue
norm_val = _get_at_path(normalized, path)
if not isinstance(norm_val, str):
continue
raw_val, ambiguous = _resolve_raw_string_for_scalar_path(raw, path)
if ambiguous or raw_val is None:
continue
if len(raw_val) <= len(norm_val):
continue
nt = norm_val.strip()
if nt:
if nt not in raw_val:
lim = min(80, len(nt))
if lim == 0 or not raw_val.startswith(nt[:lim]):
continue
set_at_path(normalized, path, raw_val)
return normalized
def aggregate_list_fields(data: Dict[str, Any], aggregate_paths: List[str]) -> Dict[str, Any]:
aggregated: Dict[str, Any] = {}
for base_path in aggregate_paths: