"""Reproduce approved Los Angeles schedule summaries from public factual cells. No network requests or raw page inputs. Notice interpretations remain reviewed inputs. """ from pathlib import Path from collections import Counter,OrderedDict import csv,sys,re,json,hashlib sys.dont_write_bytecode=True ROOT=Path(__file__).resolve().parent OUT=Path(sys.argv[1]) if len(sys.argv)>1 else Path.cwd()/'reproduced-la' OUT.mkdir(parents=True,exist_ok=True) DAYS=['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'] def read(name): with (ROOT/name).open(encoding='utf-8',newline='') as f:return list(csv.DictReader(f)) def write(name,rows): with (OUT/name).open('w',encoding='utf-8',newline='') as f: w=csv.DictWriter(f,fieldnames=list(rows[0]),lineterminator='\n');w.writeheader();w.writerows(rows) def minute(t): m=re.fullmatch(r'(\d{1,2}):(\d{2})\s*(am|pm)',t.lower().replace('.','').strip());assert m h,n=int(m[1]),int(m[2]);assert 1<=h<=12 and 0<=n<=59 return (h%12+(12 if m[3]=='pm' else 0))*60+n cells=read('all-511-source-cited-day-cells.csv');notices=read('source-notice-adjudication.csv') assert len(cells)==511 and len(notices)==6 records=OrderedDict() for c in cells: raw=c['literal_hours'];status=c['status'] if raw=='':expected=['unknown','','','',''] elif raw.lower()=='closed':expected=['listed_closed','','','0','0'] else: a,b=map(minute,re.split(r'\s*[-–]\s*',raw));assert b>a expected=['listed_interval',str(a),str(b),str(b-a),str(max(0,min(b,1200)-max(a,1080)))] assert [c[k] for k in ['status','start','end','minutes','after_18_minutes']]==expected key=c['source_url'];r=records.setdefault(key,{'branch_name':c['branch_name'],'source_url':key,'retrieved_at_utc':c['retrieved_at_utc'],'notice_presence':c['exception_notice_present']=='True','days':{}}) assert r['branch_name']==c['branch_name'] and r['retrieved_at_utc']==c['retrieved_at_utc'] and r['notice_presence']==(c['exception_notice_present']=='True') assert c['day'] not in r['days'];r['days'][c['day']]=c assert len(records)==73 for r in records.values(): assert list(r['days'])==DAYS complete=all(c['minutes']!='' for c in r['days'].values());r['hours_ambiguous']=not complete r['weekly_minutes']=sum(int(c['minutes']) for c in r['days'].values()) if complete else '' r['weekday_after_18_minutes']=sum(int(r['days'][d]['after_18_minutes']) for d in DAYS[:5]) if complete else '' r['weekend_minutes']=sum(int(r['days'][d]['minutes']) for d in DAYS[5:]) if complete else '' assert {n['source_url'] for n in notices}=={k for k,r in records.items() if r['notice_presence']} for n in notices: r=records[n['source_url']];assert n['branch_name']==r['branch_name'] and n['retrieved_at_utc']==r['retrieved_at_utc'];assert (n['full_regular_grid_present']=='True')==(not r['hours_ambiguous']) complete=[r for r in records.values() if not r['hours_ambiguous']];unflagged=[r for r in complete if not r['notice_presence']] assert len(complete)==70 and len(unflagged)==67 and sum(c['status']=='unknown' for c in cells)==21 daily=[{'day':d,'listed_open_records':sum(r['days'][d]['status']=='listed_interval' for r in records.values()),'listed_closed_records':sum(r['days'][d]['status']=='listed_closed' for r in records.values()),'unknown_or_unparsed_records':sum(r['days'][d]['minutes']=='' for r in records.values()),'after_18_records':sum(int(r['days'][d]['after_18_minutes'] or 0)>0 for r in records.values()),'denominator':73} for d in DAYS] sensitivity=[{'day':d,'unflagged_records_with_interval':sum(r['days'][d]['status']=='listed_interval' for r in unflagged),'unflagged_after_18_records':sum(int(r['days'][d]['after_18_minutes'] or 0)>0 for r in unflagged),'unflagged_subset_denominator':67,'whole_directory_denominator':73} for d in DAYS] branch=[{k:r[k] for k in ['branch_name','weekly_minutes','weekday_after_18_minutes','weekend_minutes','notice_presence','hours_ambiguous','source_url','retrieved_at_utc']} for r in records.values()] dist=Counter(r['weekly_minutes'] for r in complete);weekly=[{'weekly_scheduled_hours':k/60,'records':v,'denominator_complete_records':70} for k,v in sorted(dist.items())] for name,rows in [('day-window-summary.csv',daily),('unflagged-schedule-sensitivity.csv',sensitivity),('all-73-branch-schedules.csv',branch),('weekly-hours-distribution.csv',weekly)]:write(name,rows) proof={'source_records':len(records),'source_cells':len(cells),'known_cells':490,'unknown_cells':21,'notices':len(notices),'complete_grids':len(complete),'no_notice_complete_grids':len(unflagged),'literal_intervals_match_parsed_minutes':True,'notice_links_and_timestamps_match':True,'source_notice_interpretations_are_inputs_not_automated_findings':True} (OUT/'reproduction-checks.json').write_text(json.dumps(proof,indent=2)+'\n',encoding='utf-8',newline='\n')