"""Finite-panel sampling experiment. No inferred buyer population or validated confidence coverage.
Re-run: python3 public/research/how-many-prompts-to-measure-ai-visibility/study.py
Requires numpy. All inputs are sanitized observations in the public download.
"""
import csv,collections,hashlib,json,math,datetime
from pathlib import Path
import numpy as np
base=Path('public/research/how-many-prompts-to-measure-ai-visibility'); rows=list(csv.DictReader((base/'observations.csv').open())); rng=np.random.default_rng(20261006)
assert len({r['answer_key'] for r in rows})==len(rows)
metrics=[]
def add(group,key,val,n='',d=''):metrics.append(dict(group=group,metric=key,value=val,numerator=n,denominator=d))
for key,value in [('answers',len(rows)),('questions',len({r['question_key'] for r in rows})),('markets',len({r['market'] for r in rows})),('aligned_answers',sum(r['extraction_status']=='aligned' for r in rows)),('excluded_answers',sum(r['extraction_status']!='aligned' for r in rows)),('distinct_dates',len({r['day'] for r in rows})),('duplicate_texts',len(rows)-len({(r['question_key'],r['engine'],r['text_key']) for r in rows}))]:add('corpus',key,value)
for market in sorted({r['market'] for r in rows}):
 rs=[r for r in rows if r['market']==market];add(market,'answers',len(rs));add(market,'questions',len({r['question_key'] for r in rs}));add(market,'excluded',sum(r['extraction_status']!='aligned' for r in rs))
valid=[r for r in rows if r['extraction_status']=='aligned']
groups=collections.defaultdict(list)
for r in valid:groups[(r['market'],r['engine'])].append(r)
series=[];draws=[]
for (market,engine),rs in sorted(groups.items()):
 qs=collections.defaultdict(list)
 for r in rs:qs[r['question_key']].append(r)
 qs={q:rr for q,rr in sorted(qs.items()) if len(rr)>=10}
 if len(qs)<10:continue
 # Five most frequently counted brands on the eligible panel: descriptive selection, not an unbiased brand sample.
 count=collections.Counter(b for rr in qs.values() for r in rr for b in set(r['brands'].split('|')) if b)
 for brand,_ in count.most_common(5):
  values=[np.array([int(brand in r['brands'].split('|')) for r in rr]) for rr in qs.values()]
  reference=float(np.mean([v.mean() for v in values]));sid=f'{market}|{engine}|{brand}'
  series.append(dict(series=sid,market=market,engine=engine,questions=len(values),reference_rate=reference))
  for k,runs in [(1,10),(5,2),(10,1),(5,10),(10,5)]:
   for draw in range(200):
    ids=rng.choice(len(values),k,replace=False)
    estimate=float(np.mean([rng.choice(values[i],runs,replace=False).mean() for i in ids]))
    draws.append(dict(series=sid,questions=k,runs=runs,draw=draw,reference_rate=reference,estimate=estimate,error_points=100*(estimate-reference)))
add('experiment','series',len(series));add('experiment','draws_per_design_per_series',200)
for k,runs in [(1,10),(5,2),(10,1),(5,10),(10,5)]:
 dr=[x for x in draws if x['questions']==k and x['runs']==runs];errors=np.array([x['error_points'] for x in dr]);g=f'{k}q_{runs}r'
 add(g,'rmse_points',float(np.sqrt(np.mean(errors**2))));add(g,'median_absolute_error_points',float(np.median(abs(errors))));add(g,'p95_absolute_error_points',float(np.quantile(abs(errors),.95)));add(g,'draws',len(dr))
# A 30/180-day descriptive comparison on precisely the same question/engine/brand cells.
window_changes=[]
for s in series:
 rs=groups[(s['market'],s['engine'])];brand=s['series'].rsplit('|',1)[1];byq=collections.defaultdict(list)
 for r in rs:byq[r['question_key']].append(r)
 common=[rr for rr in byq.values() if len(rr)>=10 and sum(r['day']>='2026-09-06' for r in rr)>=2]
 if len(common)<5:continue
 long=float(np.mean([np.mean([brand in r['brands'].split('|') for r in rr]) for rr in common]));short=float(np.mean([np.mean([brand in r['brands'].split('|') for r in rr if r['day']>='2026-09-06']) for rr in common]))
 window_changes.append(dict(series=s['series'],questions=len(common),long_rate=long,short_rate=short,difference_points=100*(short-long)))
add('windows','matched_series',len(window_changes))
if window_changes:add('windows','median_absolute_difference_points',float(np.median([abs(x['difference_points']) for x in window_changes])))
for name,rs in [('dataset.csv',metrics),('series.csv',series),('windows.csv',window_changes)]:
 with (base/name).open('w') as f:
  if rs:w=csv.DictWriter(f,fieldnames=list(rs[0]),lineterminator="\n");w.writeheader();w.writerows(rs)
print(json.dumps({'input_sha256':hashlib.sha256((base/'observations.csv').read_bytes()).hexdigest(),'date_min':min(r['day'] for r in rows),'date_max':max(r['day'] for r in rows),'metrics':metrics},indent=2))
