# Registro de experimentos (incluye detenidos, fallidos y descartados): res/registro_experimentos.json.
# Para cada corrida: identificador, equipo, fechas (inicio = registro creado; fin = resultados), parámetros (de res/<id>.npz o de la
# cabecera del registro), terreno con su huella SHA-256 actual, versión de sim.py (último commit anterior al término), estado y motivo.
import json, os, re, glob, hashlib, subprocess, datetime
import numpy as np
MOTIVOS = {   # estados no completados y descartes, con su motivo (estado_local.log, res/quadrab_logs/estado.log y notas del estudio)
 'v12': ('cancelada', 'Cola detenida antes de iniciar: superada por v17/v18 (colector con Quebrada Honda tallada, terreno v5).'),
 'v13': ('cancelada', 'Cola detenida antes de iniciar: superada por v17/v18.'),
 'v16': ('cancelada', 'Nunca inició (la cola contaba dos veces cada proceso); reemplazada por v19 (brazo sur por el cauce oficial).'),
 'v22': ('detenida', 'Detenida a t≈600 s: su escenario (38 % al brazo sur) quedó cubierto por v24 y luego por las microsimulaciones del campus; cupo para v29.'),
 'v23': ('detenida', 'Detenida a t≈600 s: escenario extremo (55 % al brazo sur) con el mismo doble conteo de lluvia; reemplazada por v28.'),
 'v24': ('detenida', 'Detenida a t≈600 s por sobrecarga de quadrab: su pregunta la cubren las microsimulaciones del campus.'),
 'm10': ('detenida', 'Detenida a t≈1200 s para liberar cupo a las microsimulaciones de lluvia; el primer intento falló por la sintaxis de --punto.'),
 'mr27': ('en observación', 'Al iniciar la lluvia ANUGA agregó ~657 m³ por celdas negativas (≈1,5 % de los 43.899 m³ de lluvia); después <0,05 %. Se decide al terminar con el residuo de balance.'),
 'v26_fallida_sin_cuencas': ('fallida', 'Primer intento en quadrab: faltaban cuenca_H1/S1/S2.geojson para --lluvia_neta (FileNotFoundError); se copiaron y v26 se relanzó a las 10:42.'),
 'v25': ('en observación', 'IEC 78 depende del punto del campus (0,10 m con paso normal), igual que v20; no se usa para seleccionar. IEC sin ese punto: 72. Cv 0,43 no reduce la extensión fuera de la zona oficial (0,346 vs 0,344).'),
 'm40': ('detenida', 'Detenida a t≈600 s para priorizar las microsimulaciones de fluido líquido.'),
}
DESCARTES = {'v2_descartadas': 'Generación v2 descartada tras la revisión A2: entrada de momento y equilibrio corregidos.',
             'v3_cribado_parcial': 'Cribado v3 parcial archivado al rehacer la verificación.',
             'v3_spot_cancelado': 'Terreno fotogramétrico SPOT 2014: corridas canceladas por depresiones espurias y desfase vertical.',
             'v3c_borde_transmisivo': 'Borde transmisivo de ANUGA: creaba hasta +20 % de masa al llegar el frente; repetidas con borde de solo salida.',
             'cancelados': 'Corridas canceladas antes de terminar (cola reorganizada).'}
def sha(p):
    if not os.path.exists(p): return None
    h = hashlib.sha256()
    with open(p, 'rb') as f:
        for b in iter(lambda: f.read(1 << 20), b''): h.update(b)
    return h.hexdigest()[:16]
def commit_antes(ts):
    if ts is None: return None
    r = subprocess.run(['git', 'log', '-1', f'--before={datetime.datetime.fromtimestamp(ts).isoformat()}', '--format=%h', '--', 'sim.py'], capture_output=True, text=True)
    return r.stdout.strip() or None
fecha = lambda ts: datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M') if ts else None
SH = {}
reg = []
logs = {os.path.basename(p)[:-4]: (p, 'oficina') for p in glob.glob('res/*.log')}
logs.update({os.path.basename(p)[:-4]: (p, 'quadrab') for p in glob.glob('res/quadrab_logs/*.log') if os.path.basename(p)[:-4] not in logs or os.path.basename(p).startswith('m')})
for cid, (lp, equipo) in sorted(logs.items()):
    if not re.match(r'^[a-z]{1,3}\d{1,3}[a-z0-9_]*$', cid) or cid in ('estado', 'estado_local'): continue
    txt = open(lp, errors='replace').read()
    npz = f'res/{cid}.npz'; params = None; ts_fin = os.path.getmtime(npz) if os.path.exists(npz) else None
    if ts_fin:
        try: params = json.loads(str(np.load(npz, allow_pickle=False)['params']))
        except Exception: params = None
    cab = txt.splitlines()[1][:200] if len(txt.splitlines()) > 1 else ''
    terreno = (params or {}).get('terreno') or (re.search(r'terreno[_\w]*\.npz', txt).group(0) if re.search(r'terreno[_\w]*\.npz', txt) else None)
    if terreno and terreno not in SH: SH[terreno] = sha(terreno)
    if cid in MOTIVOS and not (MOTIVOS[cid][0] == 'en observación' and ts_fin): estado, motivo = MOTIVOS[cid]
    elif cid in MOTIVOS and ts_fin: estado, motivo = 'completada', MOTIVOS[cid][1]
    elif 'Traceback' in txt or 'error:' in txt: estado, motivo = 'fallida', (re.findall(r'(\w*Error[^\n]*|error:[^\n]*)', txt) or ['error'])[-1][:160]
    elif ts_fin and 'listo en' in txt: estado, motivo = 'completada', ''
    elif ts_fin: estado, motivo = 'completada', 'resultados presentes'
    else: estado, motivo = 'en curso o sin resultados', ''
    reg.append({'id': cid, 'equipo': equipo, 'inicio': fecha(os.path.getctime(lp)), 'fin': fecha(ts_fin), 'estado': estado, 'motivo': motivo,
                'terreno': terreno, 'sha256_terreno_actual': SH.get(terreno), 'sim_py_commit': commit_antes(ts_fin or os.path.getmtime(lp)),
                'parametros': {k: params[k] for k in ('origen', 'vol', 'qp', 'suelo', 'cv', 'cv_faldeo', 'cfl', 'lluvia', 'lluvia_neta', 'colector', 'radio_colector', 'fs1', 'dominio') if params and k in params} if params else cab})
for c in ('v12', 'v13', 'v16'):
    if not any(r['id'] == c for r in reg): reg.append({'id': c, 'equipo': 'quadrab' if c != 'v16' else 'oficina', 'inicio': None, 'fin': None, 'estado': MOTIVOS[c][0], 'motivo': MOTIVOS[c][1], 'terreno': None, 'sha256_terreno_actual': None, 'sim_py_commit': None, 'parametros': None})
for d, m in DESCARTES.items():
    ids = sorted({os.path.basename(p).split('.')[0] for p in glob.glob(f'res/{d}/*')})
    reg.append({'id': f'[{d}]', 'equipo': 'oficina', 'inicio': None, 'fin': None, 'estado': 'descartada', 'motivo': m + f' Corridas: {", ".join(ids)}.', 'terreno': None, 'sha256_terreno_actual': None, 'sim_py_commit': None, 'parametros': None})
json.dump({'generado': datetime.datetime.now().strftime('%Y-%m-%d %H:%M'), 'nota': 'La huella del terreno es la del archivo actual; los terrenos no se modifican después de usarse (cada variante tiene archivo propio).', 'experimentos': reg},
          open('res/registro_experimentos.json', 'w'), ensure_ascii=False, indent=1)
from collections import Counter
print(len(reg), Counter(r['estado'] for r in reg))
