"""F4-conf-02: verify six assertions against cited source extracts. Reads only data/; writes out/results.json deterministically. No randomness.""" import json, math, os, re, hashlib ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) D = os.path.join(ROOT, "data") OUT = os.path.join(ROOT, "out") DCR, RES = "arxiv-2609.27732v1", "arxiv-2606.14621v1" def lines(p): with open(p, encoding="utf-8") as f: return f.read().split("\n") src = {s: lines(os.path.join(D, "sources", s + ".txt")) for s in (DCR, RES)} assertions = json.load(open(os.path.join(D, "assertions.json"), encoding="utf-8")) assert [a["id"] for a in assertions] == ["A1", "A2", "A3", "A4", "A5", "A6"] def find(s, pat, start=0): for i, l in enumerate(src[s][start:], start): if re.search(pat, l): print(f" [{s} line {i+1}] {l.strip()[:200]}") return i raise ValueError(f"pattern {pat!r} not found in {s}") def nums(l): return [float(x.replace(",", "")) for x in re.findall(r"-?\d+(?:,\d{3})*\.\d+|-?\d+", l)] def cells(l): return [c.strip() for c in l.split("|")] def val(c): # strip stars / % from a table cell return float(re.sub(r"[*%()]", "", c)) pct = lambda b: 100 * (math.exp(b) - 1) V, L, R = {}, {}, {} # ---- Table 6 (DCR) ---- print("Table 6 (overall ATT):") t6 = find(DCR, r"^Table 6:") i_att = find(DCR, r"^ATT \|", t6) i_se = i_att + 2; print(f" [{DCR} line {i_se+1}] {src[DCR][i_se]}") i_pc = find(DCR, r"^% change", t6) att6 = cells(src[DCR][i_att])[1:]; se6 = [val(c) for c in cells(src[DCR][i_se])[1:]] pc6 = [val(c) for c in cells(src[DCR][i_pc])[1:]] # columns: 1 km, 3 km, 5 km b5, s5, stars5 = val(att6[2]), se6[2], att6[2].count("*") b3 = val(att6[1]) # A1 z5 = abs(b5) / s5 a1_ok = (b5 == -0.431 and s5 == 0.210 and stars5 == 2 and pc6[2] == -35.0 and round(pct(b5), 1) == -35.0 and z5 > 1.96) V["A1"] = "supported" if a1_ok else "not_supported" L["A1"] = f'{DCR} | Table 6 | row "ATT" / "% change" | column "5 km"' R["A1"] = (f"Table 6 prints ATT {att6[2]} with SE ({s5:.3f}) (** = p<0.05; |b/SE|={z5:.2f}) and % change {pc6[2]}%, " f"and 100(exp({b5})-1)={pct(b5):.1f}%, so sign, magnitude, SE, 5% significance and the 35.0% reduction all hold.") # A3 a3_ok = round(abs(pct(b3)), 1) == 28.3 and abs(pc6[1]) == 28.3 V["A3"] = "supported" if a3_ok else "not_supported" L["A3"] = f'{DCR} | Table 6 | row "% change" | column "3 km"' R["A3"] = (f"The ATT {b3} is in log points; Table 6 prints the implied change as {pc6[1]}% " f"(100(exp({b3})-1)={pct(b3):.1f}%), not a 28.3% reduction, which wrongly equates log points with percent.") # ---- Table 7 (DCR) ---- print("Table 7 (ATT by outcome):") t7 = find(DCR, r"^Table 7:") i_hiv = find(DCR, r"^HIV \|", t7) i_hep = find(DCR, r"^Hepatitis \|", t7) print(f" [{DCR} line {i_hep+3}] {src[DCR][i_hep+2]}") hiv = cells(src[DCR][i_hiv]); hep = cells(src[DCR][i_hep]); hep_se = cells(src[DCR][i_hep + 2]) hiv1 = val(hiv[1]) V["A2"] = "supported" if hiv1 < 0 else "not_supported" L["A2"] = f'{DCR} | Table 7 | row "HIV" | column "1 km"' R["A2"] = (f"Table 7 prints the 1 km HIV ATT as {hiv[1]} (positive, SE {cells(src[DCR][i_hiv+2])[1]}), " f"not -0.063, so the sign and the claimed fall in HIV hospitalisations are contradicted.") bh, sh = val(hep[3]), val(hep_se[3]) ci196 = (bh - 1.96 * sh, bh + 1.96 * sh); ci2 = (bh - 2 * sh, bh + 2 * sh); ci1 = (bh - sh, bh + sh) excl = ci196[1] < 0 or ci196[0] > 0 claimed = (-0.66, -0.16) a4_ok = excl and abs(ci196[0] - claimed[0]) < 0.02 and abs(ci196[1] - claimed[1]) < 0.02 V["A4"] = "supported" if a4_ok else "not_supported" L["A4"] = f'{DCR} | Table 7 | row "Hepatitis" | column "5 km"' R["A4"] = (f"With ATT {hep[3]} and SE {hep_se[3]} (no stars), the 95% CI is [{ci196[0]:.3f}, {ci196[1]:.3f}] " f"(±2·SE: [{ci2[0]:.3f}, {ci2[1]:.3f}]), which includes zero; the claimed [-0.66, -0.16] is only ±1 SE " f"([{ci1[0]:.3f}, {ci1[1]:.3f}]).") # A5: sex heterogeneity print("Search for any sex/gender breakdown in the DCR extract:") hits = [i for i, l in enumerate(src[DCR]) if re.search(r"\b(men|women|male|female|sex|gender)\b", l, re.I)] for i in hits: print(f" [{DCR} line {i+1}] {src[DCR][i][:200]}") print(f" -> {len(hits)} matching lines") V["A5"] = "not_assessable" if not hits else "REVIEW" L["A5"] = f"not reported in {DCR}" R["A5"] = ("The extract reports ATTs only by buffer (Table 6), diagnostic category (Table 7) and estimator (Table A19); " "no estimate by sex appears anywhere, so the men-vs-women comparison can be neither confirmed nor refuted.") # ---- Table 2 (RESEA) ---- print("Table 2 (RESEA):") t2 = find(RES, r"^Outcome \| Treatment \| Control") i_ui = find(RES, r"^Weeks on UI \|", t2) ui = cells(src[RES][i_ui]) ctrl = nums(ui[2])[0]; ate = nums(ui[4]); find(RES, r"11\.5% decrease") ate_b, ate_lo, ate_hi = ate[0], ate[-2], ate[-1] rel = 100 * abs(ate_b) / ctrl a6_ok = (ate_b == -1.99 and (ate_lo, ate_hi) == (-2.24, -1.75) and ctrl == 17.3 and round(rel, 1) == 11.5) V["A6"] = "supported" if a6_ok else "not_supported" L["A6"] = f'{RES} | Table 2 | row "Weeks on UI" | column "Average treatment effect estimate"' R["A6"] = (f"Table 2 prints the ATE as {ate_b} weeks (95% CI [{ate_lo}, {ate_hi}]) and a control mean of {ctrl} weeks; " f"1.99/17.3 = {rel:.1f}%, matching the 11.5% decrease stated in the results text.") data_issues = [ {"issue": f"{DCR}: Table 6 prints the 3 km % change as -24.7%, whereas 100(exp(-0.283)-1) = -24.65% rounds to -24.6% (a last-digit rounding difference, possibly from an unrounded coefficient).", "handling": "Immaterial for A3: both values are far from the asserted 28.3%."}, {"issue": f"{DCR}: the CS-DR 5 km ATT -0.431 carries SE (0.210) in Table 6 but SE (0.218) in Appendix Table A19, which states the same specification (5 km, 50 treated, 608 control, 17,250 obs).", "handling": "A1 cites Table 6 and quotes SE 0.210, so Table 6 is used; the verdict (significant at 5%) is unchanged under SE 0.218 (|z|=1.98>1.96)."}, {"issue": f"{DCR}: Table A19 note says 640 never-treated comparison regions, while the A19 table body, Table 6 and Table 7 say 608.", "handling": "Recorded only; no assertion depends on the control-region count."}, {"issue": f"{DCR}: the abstract says fatal outcomes 'fall by 35%', but Table 7 gives -0.354 at 5 km, i.e. 100(exp(-0.354)-1) = -29.8%, matching Section 6.1 ('approximately 30%'); the text also calls HIV estimates 'close to zero' although the 5 km HIV ATT is -0.340.", "handling": "Recorded only; tables are treated as authoritative over narrative text; no assertion depends on these statements."}, {"issue": f"{RES}: results text writes the weeks-on-UI CIs as positive magnitudes ('[1.75 weeks, 2.24 weeks]') for a reduction, and contains a rendering defect 'p ¡ 0.001' for p < 0.001.", "handling": "Interpreted as the same interval as Table 2 [-2.24, -1.75] expressed as reduction magnitudes; Table 2 used as locator for A6."}, ] def wcl(p): with open(p, "rb") as f: return f.read().count(b"\n") n_input_rows = { "assertions.json": len(assertions), "README.md": wcl(os.path.join(D, "README.md")), f"sources/{DCR}.txt": wcl(os.path.join(D, "sources", DCR + ".txt")), f"sources/{RES}.txt": wcl(os.path.join(D, "sources", RES + ".txt")), } vals = list(V.values()) assert all(v in ("supported", "not_supported", "not_assessable") for v in vals) ids = ["A1", "A2", "A3", "A4", "A5", "A6"] res = { "task_id": "F4-conf-02", "verdicts": {k: V[k] for k in ids}, "locators": {k: L[k] for k in ids}, "reasons": {k: R[k] for k in ids}, "n_supported": vals.count("supported"), "n_not_supported": vals.count("not_supported"), "n_not_assessable": vals.count("not_assessable"), "feasibility": "complete", "feasibility_reason": None, "data_issues": data_issues, "n_input_rows": n_input_rows, "n_analysed": len(assertions), "random_seed": None, "baseline_naive_all_supported_verdicts": {k: "supported" for k in ids}, "derived": { "A1_pct_from_log": round(pct(b5), 2), "A1_z": round(z5, 3), "A1_z_with_A19_se_0218": round(abs(b5) / 0.218, 3), "A3_pct_from_log": round(pct(b3), 2), "A4_ci95_1p96": [round(ci196[0], 3), round(ci196[1], 3)], "A4_ci95_2p0": [round(ci2[0], 3), round(ci2[1], 3)], "A4_ci_pm1se": [round(ci1[0], 3), round(ci1[1], 3)], "A5_sex_term_hits": len(hits), "A6_relative_pct": round(rel, 2), }, } os.makedirs(OUT, exist_ok=True) with open(os.path.join(OUT, "results.json"), "w", encoding="utf-8") as f: json.dump(res, f, indent=2, ensure_ascii=False, sort_keys=False) print("Verdicts:", res["verdicts"]) print("Counts: supported=%d not_supported=%d not_assessable=%d" % (res["n_supported"], res["n_not_supported"], res["n_not_assessable"]))