"""Apply the 2026-09-24 HOLD re-check to final_links.json: every HOLD scheme becomes LINK / RELATED / NONE.

Inputs: review/h_*_in.jsonl (the packets), review/h_*_out.jsonl (the web-research verdicts),
review/_hold_adjudication.json (Claude's overrides, keyed by case; optional).
A LINK below 90% confidence is not a link — it becomes NONE ("still unverified").
Backs up the pre-re-check final_links.json to superseded/ once; refuses to write if any HOLD case has no verdict.
"""
import glob, json, os, shutil, sys, collections

D = '/var/www/html/peta/storage/app/propertylab-catalogue-match'; R = f'{D}/review'
cat = json.load(open(f'{D}/catalogue_my_projects.json'))
idx = {c['uuid']: i for i, c in enumerate(cat)}
links = json.load(open(f'{D}/final_links.json'))

packets = {}
for fn in sorted(glob.glob(f'{R}/h_*_in.jsonl')):
    for line in open(fn):
        if line.strip():
            p = json.loads(line); packets[p['case']] = p
verdicts = {}
for fn in sorted(glob.glob(f'{R}/h_*_out.jsonl')):
    for line in open(fn):
        if line.strip():
            v = json.loads(line); verdicts[v['case']] = v
adj = json.load(open(f'{R}/_hold_adjudication.json')) if os.path.exists(f'{R}/_hold_adjudication.json') else {}

missing = sorted(set(packets) - set(verdicts) - set(adj))
if missing:
    sys.exit(f'{len(missing)} HOLD cases have no verdict yet: {missing[:20]}')

backup = f'{D}/superseded/final_links-before-hold-recheck.json'
if not os.path.exists(backup):
    shutil.copy(f'{D}/final_links.json', backup)

by_scheme = {o['scheme_id']: o for o in links}
tally = collections.Counter()
for case, p in packets.items():
    o = by_scheme[p['scheme_id']]
    if o['link'] != 'HOLD' and not o['path'].startswith('HOLD re-check'):
        sys.exit(f'{case}: scheme {p["scheme_id"]} is {o["link"]}, not HOLD — packets are stale')
    v = dict(verdicts.get(case, {})); how = 'HOLD re-check 2026-09-24: web research'
    if case in adj:
        v.update(adj[case]); how += ' + Claude adjudication'
    decision, conf = v['decision'], int(v.get('confidence_pct') or 0)
    uuid = v.get('uuid') or (p['catalogue_candidates'][v['cand']]['uuid'] if v.get('cand') is not None else None)
    if decision == 'LINK' and conf < 90:
        decision, v['reason'] = 'NONE', 'still unverified (' + str(conf) + '%) — ' + v.get('reason', '')
    if decision in ('LINK', 'RELATED'):
        if uuid not in idx:
            sys.exit(f'{case}: {decision} names a uuid that is not a MY catalogue project: {uuid}')
        o['cat_i'] = idx[uuid]
    o.update(link=decision, confidence=conf, path=how, note=v.get('reason', ''))
    tally[(p['segment'], decision)] += 1

# Corrections to schemes decided in earlier rounds, found while re-checking HOLD (e.g. two realtycheck
# schemes for one building must land on the same catalogue row — the one with an EdgeProp source).
corrections = json.load(open(f'{R}/_link_corrections.json')) if os.path.exists(f'{R}/_link_corrections.json') else []
for c in corrections:
    o = by_scheme[c['scheme_id']]
    if c['uuid'] not in idx:
        sys.exit(f'correction for {c["scheme_id"]} names an unknown uuid {c["uuid"]}')
    o['cat_i'] = idx[c['uuid']]
    if c.get('link'):
        o['link'] = c['link']
    if 'correction 2026-09-24' not in o['path']:
        o['path'] += ' → correction 2026-09-24'
        o['note'] = c['reason'] + ' — ' + (o['note'] or '')

json.dump(links, open(f'{D}/final_links.json', 'w'))
print('corrections applied:', len(corrections))
for seg in ('high-rise', 'landed', 'commercial'):
    print(f'{seg:11s}', {d: tally[(seg, d)] for d in ('LINK', 'RELATED', 'NONE')})
print('overridden by adjudication:', len(adj), '| totals now:', collections.Counter(o['link'] for o in links))
