# -*- coding: utf-8 -*-
"""
VERIFICATIE PRO KIEST KLEUR - volledige herberekening van alle gepubliceerde cijfers
Bron: HowTheyVote.eu, release 2026-07-11 (open licentie, naamsvermelding)
  https://github.com/HowTheyVote/data/releases/download/2026-07-11/votes.csv.gz
  https://github.com/HowTheyVote/data/releases/download/2026-07-11/member_votes.csv.gz
  https://github.com/HowTheyVote/data/releases/download/2026-07-11/oeil_subjects.csv.gz
  https://github.com/HowTheyVote/data/releases/download/2026-07-11/oeil_subject_votes.csv.gz
Draaien: python3 verificatie_volledig.py   (bestanden in ./data/ of ./, .csv of .csv.gz)
Uitvoer: cijferregister.md + exitcode 0 (alles PASS) / 1 (afwijkingen)
"""
import pandas as pd, numpy as np, os, sys

def lees(naam, **kw):
    for p in (f'data/{naam}.csv', f'{naam}.csv', f'data/{naam}.csv.gz', f'{naam}.csv.gz'):
        if os.path.exists(p): return pd.read_csv(p, **kw)
    raise FileNotFoundError(naam)

print('bestanden laden...')
votes = lees('votes')
mv    = lees('member_votes')
subj  = lees('oeil_subjects')
sv    = lees('oeil_subject_votes')
votes['timestamp'] = pd.to_datetime(votes['timestamp'], format='mixed')

T0 = pd.Timestamp('2024-07-16')
vt10 = votes[votes.timestamp >= T0].copy()
scope = set(vt10['id'])
m = mv[mv.vote_id.isin(scope)].copy()

GL5 = {96725:'Eickhout',197772:'Strik',197870:'Van Sparrentak',257437:'Vieira',278815:'K\u00e2hya'}
PV4 = {197782:'Chahim',218347:'Reuten',5392:'Wolters',91636:'Maij'}
BLOK = set(GL5)|set(PV4)
DRIE = ('FOR','AGAINST','ABSTENTION'); TWEE = ('FOR','AGAINST')

def maj(df, allowed):
    sub = df[df.position.astype(str).isin(allowed)]
    c = sub.groupby(['vote_id','position'], observed=True).size().unstack(fill_value=0)
    c = c.reindex(columns=list(allowed), fill_value=0)
    mx = c.max(axis=1); ok = (c.eq(mx, axis=0)).sum(axis=1) == 1
    return c.idxmax(axis=1).where(ok)

def standpunten(mm, allowed=DRIE):
    return dict(
        gl   = maj(mm[mm.member_id.isin(GL5)], allowed),
        pv   = maj(mm[mm.member_id.isin(PV4)], allowed),
        blok = maj(mm[mm.member_id.isin(BLOK)], allowed),
        g    = maj(mm[mm.group_code=='GREEN_EFA'], allowed),
        s    = maj(mm[mm.group_code=='SD'], allowed))

def drielaags(st):
    gs = pd.concat([st['g'].rename('g'), st['s'].rename('s')], axis=1).dropna()
    coll = gs[gs.g != gs.s]
    bb = st['blok'].reindex(coll.index)
    geen = len(coll.index.difference(st['blok'].index))
    d = pd.concat([coll, bb.rename('b')], axis=1).dropna()
    fg = int((d.b==d.g).sum()); fs = int((d.b==d.s).sum())
    return dict(coll=len(coll), pat=int(bb.isna().sum())-geen, geen=geen,
                koos=len(d), fg=fg, fs=fs, eigen=len(d)-fg-fs)

def pct_eens(a, b):
    x = pd.concat([a.rename('a'), b.rename('b')], axis=1).dropna()
    return 100*(x.a==x.b).mean(), len(x)

REG = []
FOUT = [0]
def check(cijfer, gepub, her, gebruikt, definitie, tol=0.05):
    if isinstance(gepub,(int,np.integer)) and isinstance(her,(int,np.integer)):
        ok = gepub == her; hs = str(int(her)); gs = str(int(gepub))
    else:
        ok = abs(float(gepub)-float(her)) <= tol; hs = f'{her:.1f}'; gs = f'{gepub:.1f}'
    if not ok: FOUT[0] += 1
    REG.append((cijfer, definitie, gs, hs, 'PASS' if ok else '**AFWIJKING**', gebruikt))
    print(('PASS ' if ok else 'FAIL ') + f'{cijfer}: gepubliceerd {gs} | herberekend {hs}')

print('hoofdanalyse...')
st = standpunten(m)
check('Stemmingen 10e periode', 5567, len(vt10), 'tegels 1/2/3; alle documenten', 'hoofdelijke stemmingen met timestamp >= 2024-07-16')
b_pct, b_n = pct_eens(st['gl'], st['pv'])
check('GL = PvdA', 88.8, b_pct, 'tegel 2 (89%); documenten', 'gelijk delegatiestandpunt; pluraliteit F/A/O, afwezig uit, gelijke stand = ongedefinieerd')
gs_pct, gs_n = pct_eens(st['g'], st['s'])
check('Fracties onderling eens', 84.0, gs_pct, 'tegel 2 (\u00b184%)', 'GREEN_EFA-meerderheid = SD-meerderheid')
D = drielaags(st)
check('Botsingen totaal', 892, D['coll'], 'tegels 3/4/7; documenten', 'beide fractiemeerderheden gedefinieerd \u00e9n verschillend')
check('Niet-botsend aandeel', 84.0, 100*(len(vt10)-D['coll'])/len(vt10), 'tegel 3 (ruim 8 van de 10)', '1 - botsingen/scope')
check('Blok-patstellingen', 297, D['pat'], 'tegel 7 (1 op de 3)', 'botsingen met ongedefinieerd blokstandpunt (gelijke stand)')
check('Patstelling-aandeel', 33.3, 100*D['pat']/D['coll'], 'tegel 7', 'patstellingen / botsingen')
check('Blok koos', 595, D['koos'], 'tegel 7; documenten', 'botsingen met gedefinieerd blokstandpunt')
check('  volgt Groenen (abs)', 386, D['fg'], 'invariant', 'blok = GREEN_EFA-meerderheid')
check('  volgt S&D (abs)', 185, D['fs'], 'invariant', 'blok = SD-meerderheid')
check('  volgt Groenen %', 64.9, 100*D['fg']/D['koos'], 'tegel 7 (65%)', 'over blok-koos')
check('  volgt S&D %', 31.1, 100*D['fs']/D['koos'], 'tegel 7 (31%)', 'over blok-koos')
check('  eigen lijn %', 4.0, 100*D['eigen']/D['koos'], 'tegel 7 (4%)', 'over blok-koos')

print('delegaties op botsingen...')
gsx = pd.concat([st['g'].rename('g'), st['s'].rename('s')], axis=1).dropna()
collx = gsx[gsx.g != gsx.s]
for kort, kol, eg, es, waar in [('GL', 'gl', 90.0, 6.7, 'infographic; pakket'), ('PvdA', 'pv', 21.1, 75.2, 'pakket (21%); tegels vervolg')]:
    dd = pd.concat([collx, st[kol].reindex(collx.index).rename('x')], axis=1).dropna()
    check(f'{kort} volgt Groenen (botsingen)', eg, 100*(dd.x==dd.g).mean(), waar, f'over botsingen waarin {kort}-standpunt gedefinieerd (n={len(dd)})')
    check(f'{kort} volgt S&D (botsingen)', es, 100*(dd.x==dd.s).mean(), waar, 'idem')

print('varianten...')
# E-i eindstemmingen
fin_ids = set(vt10[vt10.is_main==True]['id'])
stf = standpunten(m[m.vote_id.isin(fin_ids)])
check('Eindstemmingen n', 614, len(fin_ids), 'pakket p4 methode 2', 'is_main == True')
check('E-i GL=PvdA', 95.9, pct_eens(stf['gl'],stf['pv'])[0], 'antwoordblad', '')
check('E-i blok=Groenen', 95.7, pct_eens(stf['blok'],stf['g'])[0], 'pakket p4', '')
check('E-i blok=S&D', 91.5, pct_eens(stf['blok'],stf['s'])[0], 'pakket p4', '')
Df = drielaags(stf)
check('E-i botsingen/pat/koos', 78, Df['coll'], 'antwoordblad', f"pat={Df['pat']}, koos={Df['koos']}")
check('E-i volgt G/S (abs)', 43, Df['fg'], 'invariant', f"fs={Df['fs']}")
check('E-i volgt Groenen %', 64.2, 100*Df['fg']/Df['koos'], 'pakket p4 (64%)', 'over blok-koos')
check('E-i volgt S&D %', 26.9, 100*Df['fs']/Df['koos'], 'pakket p4 (27%)', 'over blok-koos')
# COD
cod_ids = set(vt10[vt10.procedure_type.astype(str).eq('COD')]['id'])
stc = standpunten(m[m.vote_id.isin(cod_ids)])
check('Wetgeving (COD) n', 755, len(cod_ids), 'pakket p4 methode 3', "procedure_type == COD (gewone wetgevingsprocedure)")
check('COD blok=Groenen', 94.8, pct_eens(stc['blok'],stc['g'])[0], 'pakket p1/p4', '')
check('COD blok=S&D', 86.0, pct_eens(stc['blok'],stc['s'])[0], 'pakket p1/p4', '')
Dc = drielaags(stc)
check('COD volgt Groenen %', 72.2, 100*Dc['fg']/Dc['koos'], 'pakket p4 (72%)', f"botsingen {Dc['coll']}, pat {Dc['pat']}, koos {Dc['koos']}, abs {Dc['fg']}/{Dc['fs']}")
check('COD volgt S&D %', 23.0, 100*Dc['fs']/Dc['koos'], 'pakket p4 (23%)', 'over blok-koos')
# zonder GBVB (6.10*)
s610 = subj[subj.code.astype(str).str.startswith('6.10')]
gbvb_votes = set(sv[sv.subject_code.isin(set(s610.code))].vote_id) if 'subject_code' in sv.columns else set(sv[sv.oeil_subject_code.isin(set(s610.code))].vote_id)
nog = scope - gbvb_votes
stg = standpunten(m[m.vote_id.isin(nog)])
check('Zonder GBVB n', 4282, len(nog), 'pakket p4 methode 4', 'scope minus stemmingen met OEIL-code 6.10*')
check('GBVB-excl blok=Groenen', 95.9, pct_eens(stg['blok'],stg['g'])[0], 'pakket p4', '')
check('GBVB-excl blok=S&D', 91.5, pct_eens(stg['blok'],stg['s'])[0], 'pakket p4', '')
Dg = drielaags(stg)
check('GBVB-excl volgt Groenen %', 66.3, 100*Dg['fg']/Dg['koos'], 'pakket p4 (66%)', f"botsingen {Dg['coll']}, pat {Dg['pat']}, koos {Dg['koos']}, abs {Dg['fg']}/{Dg['fs']}")
check('GBVB-excl volgt S&D %', 29.3, 100*Dg['fs']/Dg['koos'], 'pakket p4 (29%)', 'over blok-koos')
# E-ii Voor/Tegen
st2 = standpunten(m, TWEE)
check('F/A GL=PvdA', 92.5, pct_eens(st2['gl'],st2['pv'])[0], 'pakket p4', 'onthouding als niet-uitgebracht')
check('F/A blok=Groenen', 95.5, pct_eens(st2['blok'],st2['g'])[0], 'pakket p2/p4', '')
check('F/A blok=S&D', 94.1, pct_eens(st2['blok'],st2['s'])[0], 'pakket p2/p4 (kloof 1,4pt)', '')
D2 = drielaags(st2)
check('F/A botsingen', 763, D2['coll'], 'antwoordblad', f"pat={D2['pat']}, geen blok-stem={D2['geen']}, koos={D2['koos']}")
check('F/A volgt G/S (abs)', 311, D2['fg'], 'invariant', f"fs={D2['fs']}")
check('F/A volgt Groenen %', 56.6, 100*D2['fg']/D2['koos'], 'pakket p4 (57%)', 'over blok-koos; eigen lijn structureel 0')
check('F/A volgt S&D %', 43.4, 100*D2['fs']/D2['koos'], 'pakket p4 (43%)', 'over blok-koos')

print('splits, leden, gewichten...')
sp = pd.concat([st['gl'].rename('a'), st['pv'].rename('b')], axis=1).dropna()
check('GL-PvdA-splits', 621, int((sp.a!=sp.b).sum()), 'infographic statchip', 'beide gedefinieerd en verschillend')
mm3 = m[m.position.astype(str).isin(DRIE) & m.member_id.isin(BLOK)][['vote_id','member_id','position']].copy()
gmap = st['g']; smap = st['s']
mm3['g'] = mm3.vote_id.map(gmap); mm3['sd'] = mm3.vote_id.map(smap)
per = mm3.groupby('member_id').apply(lambda d: pd.Series({
    'n': len(d),
    'wg': 100*(d.position.astype(str)==d.g.astype(str))[d.g.notna()].mean(),
    'ws': 100*(d.position.astype(str)==d.sd.astype(str))[d.sd.notna()].mean()}), include_groups=False)
check('K\u00e2hya geteld', 278, int(per.loc[278815,'n']), 'pakket p4; captions', 'stemmen met positie F/A/O')
wg = float((per.wg*per.n).sum()/per.n.sum()); ws = float((per.ws*per.n).sum()/per.n.sum())
check('Gewogen lid vs Groenen', 92.5, wg, 'pakket p1; infographic oordeel', 'lid-match met fractiemeerderheid, gewogen naar n')
check('Gewogen lid vs S&D', 89.8, ws, 'pakket p1; infographic oordeel', '')
gl_ids = list(GL5); pv_ids = list(PV4)
glc = per.loc[[i for i in gl_ids if i in per.index]]; pvc = per.loc[[i for i in pv_ids if i in per.index]]
check('Compromis Groenen-optie GL', 1.9, 100-float((glc.wg*glc.n).sum()/glc.n.sum()), 'pakket p1', '100 - gewogen match')
check('Compromis Groenen-optie PvdA', 13.0, 100-float((pvc.wg*pvc.n).sum()/pvc.n.sum()), 'pakket p1/p2', '')
check('Compromis S&D-optie GL', 15.6, 100-float((glc.ws*glc.n).sum()/glc.n.sum()), 'pakket p1', '')
check('Compromis S&D-optie PvdA', 5.0, 100-float((pvc.ws*pvc.n).sum()/pvc.n.sum()), 'pakket p2', '')
vier_gl = [96725,197772,197870,257437]
check('GL(4) vs S&D min', 83.9, float(per.loc[vier_gl,'ws'].min()), 'pakket p2 (83,9-85,1)', 'excl. K\u00e2hya (n=278)')
check('GL(4) vs S&D max', 85.1, float(per.loc[vier_gl,'ws'].max()), 'pakket p2', '')
check('PvdA vs S&D min', 94.0, float(per.loc[pv_ids,'ws'].min()), 'pakket p2 (94,0-95,5)', '')
check('PvdA vs S&D max', 95.5, float(per.loc[pv_ids,'ws'].max()), 'pakket p2', '')

print('winnende kant, fractiegroottes, dossier...')
kamer = maj(m, DRIE)
check('Winnende kant S&D', 85.4, pct_eens(st['s'], kamer)[0], 'pakket p2 (85%)', 'fractiemeerderheid = pluraliteit voltallige parlement')
check('Winnende kant Groenen', 73.9, pct_eens(st['g'], kamer)[0], 'pakket p2 (74%)', '')
check('Winnende kant blok', 80.1, pct_eens(st['blok'], kamer)[0], 'documenten', '')
juli = set(vt10[vt10.timestamp >= '2026-07-01']['id'])
mj = mv[mv.vote_id.isin(juli)]
check('S&D-leden juli 2026', 135, int(mj[mj.group_code=='SD'].member_id.nunique()), 'pakket p2; captions', 'unieke leden met registratie in juli-stemmingen')
check('Groenen-leden juli 2026', 53, int(mj[mj.group_code=='GREEN_EFA'].member_id.nunique()), 'pakket p2; captions', '')
woon = vt10[vt10.display_title.astype(str).str.contains('Housing crisis in the European Union', na=False)]
wset = pd.concat([st['gl'].rename('a'), st['pv'].rename('b')], axis=1).reindex(woon['id']).dropna()
check('Wooncrisis verdeelde stemmingen', 68, int((wset.a!=wset.b).sum()), 'infographic annotatie; captions', 'GL en PvdA gedefinieerd \u00e9n verschillend binnen het dossier')

print('per beleidsterrein...')
subj['top'] = subj.code.astype(str).str.split('.').str[0]
sv_col = 'subject_code' if 'subject_code' in sv.columns else 'oeil_subject_code'
sva = sv[sv.vote_id.isin(scope)].merge(subj[['code','top']], left_on=sv_col, right_on='code')
va = sva[['vote_id','top']].drop_duplicates()
AREAS_PUB = {'1':(361,96.3,91.0),'2':(278,97.0,90.4),'3':(1189,95.9,89.0),'4':(937,97.5,91.6),
             '5':(139,97.5,93.2),'6':(1781,93.8,92.6),'7':(161,95.5,91.7),'8':(1566,96.7,94.3)}
blok_s = st['blok']; g_s = st['g']; s_s = st['s']
tabel_coll = {}
for top,(n_pub,jg_pub,js_pub) in AREAS_PUB.items():
    ids = set(va[va.top==top].vote_id)
    check(f'Terrein {top}: n gelabeld', n_pub, len(ids), 'verkenner', 'unieke stemmingen met OEIL-hoofdcode')
    bg = pd.concat([blok_s.rename('b'), g_s.rename('g')], axis=1).reindex(list(ids)).dropna()
    bs = pd.concat([blok_s.rename('b'), s_s.rename('s')], axis=1).reindex(list(ids)).dropna()
    check(f'Terrein {top}: blok=Groenen', jg_pub, 100*(bg.b==bg.g).mean(), 'verkenner; pakket p1', '', tol=0.1)
    check(f'Terrein {top}: blok=S&D', js_pub, 100*(bs.b==bs.s).mean(), 'verkenner; pakket p1', '', tol=0.1)
    cids = [i for i in collx.index if i in ids]
    cd = pd.concat([collx.loc[cids], blok_s.reindex(cids).rename('b')], axis=1).dropna()
    if len(cd) >= 25:
        tabel_coll[top] = dict(n=len(cd), g=100*(cd.b==cd.g).mean(), s=100*(cd.b==cd.s).mean())
print('coll per terrein (blok koos):', {k:(v['n'],round(v['g']),round(v['s'])) for k,v in tabel_coll.items()})

print('kwartaalverloop...')
q = pd.concat([st['gl'].rename('a'), st['pv'].rename('b')], axis=1).dropna()
q = q.join(vt10.set_index('id')['timestamp'])
q['kw'] = q.timestamp.dt.to_period('Q').astype(str)
qs = q.groupby('kw').apply(lambda d: 100*(d.a==d.b).mean(), include_groups=False)
check('Dieptepunt 2026K1', 81.0, float(qs.get('2026Q1', np.nan)), 'pakket p4-tekstueel; captions (81%)', 'kwartaaloverzicht GL=PvdA', tol=0.5)
vol = qs.drop('2026Q3', errors='ignore')
helling = float(np.polyfit(range(len(vol)), vol.values, 1)[0])
check('Trend per kwartaal', -0.9, helling, 'pakket p2 (indicatief)', 'lineaire trend, volle kwartalen', tol=0.25)

# ---- register wegschrijven ----
kop = f"""# CIJFERREGISTER \u00b7 PRO Kiest Kleur
Elke gepubliceerde waarde, herberekend uit de ruwe brondata (release 2026-07-11) door `verificatie_volledig.py`, onafhankelijk van de productiepipeline.
Scope: 10e zittingsperiode, 16 jul 2024 \u2013 9 jul 2026. Kernmethode: pluraliteit van Voor/Tegen/Onthouding; afwezigen uitgesloten; gelijke standen = ongedefinieerd. Drielaags botsingsmodel: **{D['coll']} botsingen \u00b7 {D['pat']} blok-patstellingen (1 op de 3) \u00b7 {D['koos']} met blokstandpunt \u2192 {D['fg']}\u00d7 Groenen / {D['fs']}\u00d7 S&D / {D['eigen']}\u00d7 eigen lijn**. Samenstelling: 'eigen lijn' (24) = 18x onthouding, 3x voor, 3x tegen; blok-onthoudingen komen ook binnen de volg-categorieen voor (76x volgt-Groenen, 18x volgt-S&D). De categorie beschrijft dus de relatie tot de fractiemeerderheden, niet de stempositie.

| Cijfer | Definitie | Gepubliceerd | Herberekend | Status | Gebruikt in |
|---|---|---|---|---|---|
"""
rijen = '\n'.join(f'| {a} | {b} | {c} | {d} | {e} | {f} |' for a,b,c,d,e,f in REG)
voet = f"""

**Resultaat: {len(REG)-FOUT[0]} van {len(REG)} controles PASS.**
Replicatie door derden: draai dit script naast de vier bronbestanden, of gebruik de blinde AI-prompt (prompt_ai_verificatie.md). De absolute invarianten (386/185, 43/18, 311/238) gelden onder elke noemerkeuze.
"""
open('cijferregister.md','w').write(kop + rijen + voet)
print(f"\\nKLAAR: {len(REG)-FOUT[0]}/{len(REG)} PASS -> cijferregister.md")
sys.exit(0 if FOUT[0]==0 else 1)
