← Files Research & Writing CopilotARCHIVED FILE
scripts/evidence_audit.py
2.41 KB · Nov 3, 2026 · 18:48 UTC
#!/usr/bin/env python3
"""Offline structure-only audit of claim/source records. Does NOT verify factual accuracy."""
import argparse,csv,json,re,sys
from pathlib import Path
VALID={'verified','source-reported','inference','opinion','contradicted','stale','unverified'}
URL_RE=re.compile(r'^https?://[^\s]+$',re.I)
DOI_RE=re.compile(r'^10\.\d{4,9}/\S+$',re.I)
def read_csv(path):
with Path(path).open(encoding='utf-8-sig',newline='') as f:return list(csv.DictReader(f))
def audit(claims,sources):
problems=[]; src={}
for i,row in enumerate(sources,2):
sid=(row.get('source_id') or '').strip(); locator=(row.get('url_or_doi') or '').strip()
if not sid: problems.append(f'sources row {i}: missing source_id');continue
if sid in src:problems.append(f'sources row {i}: duplicate source_id {sid}')
src[sid]=row
if not locator:problems.append(f'source {sid}: missing locator')
elif not(URL_RE.match(locator) or DOI_RE.match(locator)):problems.append(f'source {sid}: locator format not URL/DOI')
seen=set(); counts={}
for i,row in enumerate(claims,2):
cid=(row.get('claim_id') or '').strip();claim=(row.get('claim') or '').strip(); status=(row.get('status') or '').strip().lower()
if not cid:problems.append(f'claims row {i}: missing claim_id')
elif cid in seen:problems.append(f'claims row {i}: duplicate claim_id {cid}')
seen.add(cid)
if not claim:problems.append(f'claims row {i}: missing claim text')
if status not in VALID:problems.append(f'claim {cid or i}: invalid status {status}')
counts[status]=counts.get(status,0)+1
sids=[v.strip() for v in (row.get('source_ids') or '').split(';') if v.strip()]
if status in {'verified','source-reported','contradicted','stale'} and not sids:
problems.append(f'claim {cid or i}: evidence-required status without source')
for s in sids:
if s not in src:problems.append(f'claim {cid or i}: unknown source_id {s}')
return {'total_claims':len(claims),'total_sources':len(sources),'status_counts':counts,'structural_issues':problems,'passed':not problems,'limitations':'Checks IDs, status and locator format only; DOES NOT fetch sources or verify claims, quotes or link reachability.'}
def main(argv=None):
p=argparse.ArgumentParser(description=__doc__);p.add_argument('claims');p.add_argument('sources');a=p.parse_args(argv)
result=audit(read_csv(a.claims),read_csv(a.sources));print(json.dumps(result,indent=2));return 0 if result['passed'] else 1
if __name__=='__main__':sys.exit(main())
SHA-256: 0d9d03118d0466e2ac72ab9140ac35e8c63ce9da6165a9d2761f7d61f224108f