Jose Salazar Claude Opus 5 commited on
Commit
02b6489
·
1 Parent(s): bf9f7d1

Publicar el índice curado y evitar que el Hub acumule ficheros huérfanos

Browse files

Al publicar el índice curado (6772 → 6201 fragmentos) aparecieron dos fallos en
la ruta de publicación:

- `upload_folder` no borraba lo que ya no existe en local. LanceDB versiona por
ficheros, así que al compactar desaparecen los datos viejos y el remoto se los
quedaba: el repo tenía 2 ficheros de datos y 2 índices FTS huérfanos, 73 MB
para servir 33 MB — que además se hornean en la imagen. Ahora el remoto es un
espejo del local (`delete_patterns="*"`).

- `manifest.json` lo escribe la ingesta, pero `curar_indice.py` cambia el número
de filas después. Publicaba un recuento desfasado sin romper nada visible, que
es lo peligroso: el mensaje de commit del Hub y `fetch-index` anunciaban 6772
fragmentos sobre un índice de 6201. `curar_indice.py` lo reconcilia y
`publish-index` aborta si no cuadra.

Verificado descargando el índice publicado en limpio: 13 ficheros, 33 MB, los 51
tests de alcance en verde y la eval de recuperación en 0.814/0.941/0.912.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Files changed (2) hide show
  1. scripts/curar_indice.py +32 -0
  2. scripts/hub.py +29 -0
scripts/curar_indice.py CHANGED
@@ -19,6 +19,7 @@ demás máquinas se lo lleven; si no, sólo queda arreglado en local.
19
  from __future__ import annotations
20
 
21
  import argparse
 
22
  import sys
23
  from collections import Counter
24
  from pathlib import Path
@@ -35,6 +36,20 @@ from app.rag.alcance_corpus import ( # noqa: E402
35
  )
36
 
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  def main() -> int:
39
  parser = argparse.ArgumentParser()
40
  parser.add_argument(
@@ -83,7 +98,19 @@ def main() -> int:
83
  print(f" cambian de especie: {reetiquetados}")
84
  print(f" especies tras curar: {dict(Counter(nueva))}")
85
 
 
 
 
 
 
 
86
  if not fuera.any() and not reetiquetados:
 
 
 
 
 
 
87
  print("\nNada que hacer.")
88
  return 0
89
  if not args.aplicar:
@@ -111,6 +138,11 @@ def main() -> int:
111
  tamano = sum(f.stat().st_size for f in ruta.rglob("*") if f.is_file()) / 1e6
112
  print(f"Versiones antiguas purgadas. Tamaño en disco: {tamano:.0f} MB")
113
 
 
 
 
 
 
114
  print(f"\n✅ {total} → {len(df_quedan)} fragmentos. Recuerda `make publish-index`.")
115
  return 0
116
 
 
19
  from __future__ import annotations
20
 
21
  import argparse
22
+ import json
23
  import sys
24
  from collections import Counter
25
  from pathlib import Path
 
36
  )
37
 
38
 
39
+ def actualizar_manifiesto(ruta: Path, n_fragmentos: int) -> None:
40
+ """Deja `manifest.json` de acuerdo con lo que hay realmente en la tabla."""
41
+ destino = ruta / "manifest.json"
42
+ if not destino.exists():
43
+ print("⚠ Sin manifest.json: no se puede dejar constancia del recuento.")
44
+ return
45
+ datos = json.loads(destino.read_text(encoding="utf-8"))
46
+ antes = datos.get("n_fragmentos")
47
+ datos["n_fragmentos"] = n_fragmentos
48
+ datos["curado"] = "data/rag_alcance.json"
49
+ destino.write_text(json.dumps(datos, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
50
+ print(f"manifest.json actualizado: n_fragmentos {antes} → {n_fragmentos}")
51
+
52
+
53
  def main() -> int:
54
  parser = argparse.ArgumentParser()
55
  parser.add_argument(
 
98
  print(f" cambian de especie: {reetiquetados}")
99
  print(f" especies tras curar: {dict(Counter(nueva))}")
100
 
101
+ manifiesto = ruta / "manifest.json"
102
+ if manifiesto.exists():
103
+ declarado = json.loads(manifiesto.read_text(encoding="utf-8")).get("n_fragmentos")
104
+ if declarado != len(df_quedan):
105
+ print(f"\n⚠ manifest.json declara {declarado} fragmentos y quedarían {len(df_quedan)}.")
106
+
107
  if not fuera.any() and not reetiquetados:
108
+ if manifiesto.exists() and declarado != len(df_quedan):
109
+ if not args.aplicar:
110
+ print("Repite con --aplicar para corregir sólo el manifiesto.")
111
+ return 0
112
+ actualizar_manifiesto(ruta, len(df_quedan))
113
+ return 0
114
  print("\nNada que hacer.")
115
  return 0
116
  if not args.aplicar:
 
138
  tamano = sum(f.stat().st_size for f in ruta.rglob("*") if f.is_file()) / 1e6
139
  print(f"Versiones antiguas purgadas. Tamaño en disco: {tamano:.0f} MB")
140
 
141
+ # El manifiesto lo escribe la ingesta y aquí acaba de cambiar el número de fragmentos. Si no
142
+ # se actualiza, miente: `fetch-index` lo imprime y `publish-index` lo usa como mensaje de
143
+ # commit, así que el Hub anunciaría un recuento que no es el del índice que sirve.
144
+ actualizar_manifiesto(ruta, len(df_quedan))
145
+
146
  print(f"\n✅ {total} → {len(df_quedan)} fragmentos. Recuerda `make publish-index`.")
147
  return 0
148
 
scripts/hub.py CHANGED
@@ -43,11 +43,35 @@ def _asegurar_repo(api: HfApi, repo_id: str) -> None:
43
  )
44
 
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def publicar_indice() -> None:
47
  if not DIR_INDICE.exists():
48
  sys.exit(f"ERROR: no existe {DIR_INDICE}. Ejecuta 'make ingest' primero.")
49
  manifiesto = json.loads((DIR_INDICE / "manifest.json").read_text(encoding="utf-8"))
50
  hash_corpus = manifiesto.get("hash_corpus", "desconocido")
 
51
  api = HfApi()
52
  _asegurar_repo(api, REPO_INDICE)
53
  api.upload_folder(
@@ -55,6 +79,11 @@ def publicar_indice() -> None:
55
  repo_type="dataset",
56
  folder_path=str(DIR_INDICE),
57
  commit_message=f"Índice RAG · corpus {hash_corpus} · {manifiesto.get('n_fragmentos')} fragmentos",
 
 
 
 
 
58
  )
59
  print(f"OK: índice publicado en {REPO_INDICE} (corpus {hash_corpus})")
60
 
 
43
  )
44
 
45
 
46
+ def _comprobar_manifiesto(manifiesto: dict) -> None:
47
+ """Aborta si `manifest.json` no coincide con la tabla que se va a publicar.
48
+
49
+ El manifiesto lo escribe la ingesta, pero `curar_indice.py` cambia el número de filas
50
+ después. Publicar uno desfasado no rompe nada visible —y por eso es peligroso—: el mensaje
51
+ de commit del Hub y lo que imprime `fetch-index` anuncian un recuento que no es el del
52
+ índice que sirve, y a partir de ahí nadie sabe qué hay publicado.
53
+ """
54
+ try:
55
+ import lancedb
56
+ except ImportError:
57
+ print("AVISO: sin lancedb no se puede verificar el manifiesto; se publica a ciegas.")
58
+ return
59
+ tabla = lancedb.connect(str(DIR_INDICE)).open_table("literatura")
60
+ reales = tabla.count_rows()
61
+ declarados = manifiesto.get("n_fragmentos")
62
+ if declarados != reales:
63
+ sys.exit(
64
+ f"ABORTADO: manifest.json declara {declarados} fragmentos y la tabla tiene {reales}.\n"
65
+ f"Ejecuta 'make curar-indice ARGS=--aplicar' para reconciliarlo antes de publicar."
66
+ )
67
+
68
+
69
  def publicar_indice() -> None:
70
  if not DIR_INDICE.exists():
71
  sys.exit(f"ERROR: no existe {DIR_INDICE}. Ejecuta 'make ingest' primero.")
72
  manifiesto = json.loads((DIR_INDICE / "manifest.json").read_text(encoding="utf-8"))
73
  hash_corpus = manifiesto.get("hash_corpus", "desconocido")
74
+ _comprobar_manifiesto(manifiesto)
75
  api = HfApi()
76
  _asegurar_repo(api, REPO_INDICE)
77
  api.upload_folder(
 
79
  repo_type="dataset",
80
  folder_path=str(DIR_INDICE),
81
  commit_message=f"Índice RAG · corpus {hash_corpus} · {manifiesto.get('n_fragmentos')} fragmentos",
82
+ # El remoto debe ser un ESPEJO del local, no una acumulación. LanceDB versiona por
83
+ # ficheros: al compactar desaparecen los datos viejos, y sin esto se quedarían en el Hub
84
+ # para siempre. Medido: el repo tenía 2 ficheros de datos y 2 índices FTS huérfanos de
85
+ # versiones anteriores — 73 MB para servir 33 MB, que además se hornean en la imagen.
86
+ delete_patterns="*",
87
  )
88
  print(f"OK: índice publicado en {REPO_INDICE} (corpus {hash_corpus})")
89