SPB Git

spb/wp29_uqo

Public
12.8 KB · 238 lines python
1"""F4-conf-01: extract printed values from two arXiv extracts and derive two quantities.2Reads only data/; writes out/results.json deterministically. No randomness (random_seed = null).3Prints every matched source line so values can be traced."""4import json, os, re, math56ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))7DCR_ID, RI_ID = "arxiv-2609.27732v1", "arxiv-2606.14621v1"8DCR = os.path.join(ROOT, "data", "sources", DCR_ID + ".txt")9RI = os.path.join(ROOT, "data", "sources", RI_ID + ".txt")10README = os.path.join(ROOT, "data", "README.md")11Z = 1.96  # normal critical value for two-sided 95% intervals1213def lines_of(p):14    with open(p, encoding="utf-8") as f:15        return f.read().split("\n")1617def wc_l(p):18    with open(p, "rb") as f:19        return f.read().count(b"\n")2021def lit(s):22    """Printed literal with $, commas, stars, units removed (keeps trailing zeros)."""23    s = s.strip().replace("$", "").replace(",", "")24    s = re.sub(r"\*+", "", s)25    return re.search(r"-?\d+(?:\.\d+)?", s).group(0)2627def num(s):28    """Parse a printed number: strip $, thousands commas, stars, %, units. Keep sign."""29    s = s.strip().replace("$", "").replace(",", "")30    s = re.sub(r"\*+", "", s)31    m = re.search(r"-?\d+(?:\.\d+)?", s)32    v = m.group(0)33    return int(v) if re.fullmatch(r"-?\d+", v) else float(v)3435def cells(line):36    return [c.strip() for c in line.split("|")]3738def find_table(lines, title_prefix):39    for i, l in enumerate(lines):40        if l.startswith(title_prefix):41            return i42    raise ValueError(title_prefix)4344def dcr_table(lines, title_prefix):45    """Return header columns and dict row_label -> (coef cells, se cells or None, line numbers)."""46    i = find_table(lines, title_prefix)47    j = i + 148    while not lines[j].strip():49        j += 150    header = cells(lines[j])[1:]  # first cell is row-label column ('' or 'Outcome')51    rows, k, nonblank = {}, j + 1, []52    while k < len(lines) and not lines[k].startswith("Note:"):53        if lines[k].strip():54            nonblank.append(k)55        k += 156    for idx, ln in enumerate(nonblank):57        c = cells(lines[ln])58        if c[0] == "":59            continue  # SE line, attached below60        se, se_ln = None, None61        if idx + 1 < len(nonblank):62            nxt = cells(lines[nonblank[idx + 1]])63            if nxt[0] == "" and nxt[1].startswith("("):64                se, se_ln = nxt[1:], nonblank[idx + 1]65        rows[c[0]] = (c[1:], se, ln + 1, (se_ln + 1) if se_ln is not None else None)66    return header, rows6768def stars(cell):69    m = re.search(r"(\*+)\s*$", cell.strip())70    return len(m.group(1)) if m else 07172dl, rl = lines_of(DCR), lines_of(RI)73h6, t6 = dcr_table(dl, "Table 6:")74h7, t7 = dcr_table(dl, "Table 7:")75hA, tA = dcr_table(dl, "Table A19:")7677def get(tab, hdr, row, col, se=False):78    coef, ses, ln, sln = tab[row]79    j = hdr.index(col)80    cell = (ses if se else coef)[j]81    print(f"  matched line {sln if se else ln}: row '{row}' col '{col}' -> '{cell}'")82    v = num(cell.strip("()"))83    return v, cell8485def loc(src, tab, row, col, extra=""):86    return f'{src} | Table {tab} | row "{row}" | column "{col}"' + extra8788R, L = {}, {}89print("DCR Table 6 / Table 7 extraction:")90for key, tab, hdr, t, row, col in [91    ("dcr_att_5km", 6, h6, t6, "ATT", "5 km"),92    ("dcr_att_3km", 6, h6, t6, "ATT", "3 km"),93    ("dcr_narcotics_att_3km", 7, h7, t7, "Narcotics", "3 km"),94    ("dcr_fatal_att_1km", 7, h7, t7, "Drug-related (fatal)", "1 km"),95    ("dcr_hiv_att_5km", 7, h7, t7, "HIV", "5 km"),96]:97    R[key], cell = get(t, hdr, row, col)98    L[key] = loc(DCR_ID, tab, row, col)99    R[key + "_se"], _ = get(t, hdr, row, col, se=True)100    L[key + "_se"] = loc(DCR_ID, tab, row, col, " | standard error in parentheses on the line below the coefficient")101    if key == "dcr_hiv_att_5km":102        R["dcr_hiv_att_5km_significant_10pct"] = stars(cell) >= 1103R["dcr_obs_5km"], _ = get(t6, h6, "Observations", "5 km")104L["dcr_obs_5km"] = loc(DCR_ID, 6, "Observations", "5 km")105106# exact p-value of HIV 5 km: search Table 7 block and whole extract for a printed p-value107dcr_text = "\n".join(dl)108has_p = re.search(r"\bp\s*=\s*0?\.\d+", dcr_text) is not None109R["dcr_hiv_att_5km_p_value"] = None  # Table 7 prints stars only110L["dcr_hiv_att_5km_p_value"] = (f"not reported in {DCR_ID} (Table 7 prints only the coefficient, SE and "111                                "significance stars; no exact p-value is printed anywhere in the extract)")112assert not has_p113114# derived 95% CI from Table 6 SE115b, se = R["dcr_att_5km"], R["dcr_att_5km_se"]116R["dcr_att_5km_ci_low"] = round(b - Z * se, 4)117R["dcr_att_5km_ci_high"] = round(b + Z * se, 4)118for k in ("dcr_att_5km_ci_low", "dcr_att_5km_ci_high"):119    L[k] = f'{DCR_ID} | Table 6 | row "ATT" (and SE line below) | column "5 km" | derived: estimate -/+ 1.96 x SE'120121print("RESEA Table 2 extraction:")122def ri_table(lines, header_start):123    i = [n for n, l in enumerate(lines) if l.startswith(header_start)][0]124    hdr = cells(lines[i])125    rows, k = {}, i + 1126    while lines[k].strip():127        c = cells(lines[k]); rows[c[0]] = (c, k + 1); k += 1128    return hdr, rows129h2, t2 = ri_table(rl, "Outcome | Treatment | Control | Treatment-control difference")130ATE = "Average treatment effect estimate"131132def ate_cell(row):133    c, ln = t2[row]134    cell = c[h2.index(ATE)]135    print(f"  matched line {ln}: row '{row}' col '{ATE}' -> '{cell}'")136    est = num(cell.split(";")[0])137    lo, hi = re.search(r"\[(.+?),\s*(-?[\d,\.]+)\]", cell).groups()138    return est, num(lo), num(hi), cell139140R["ri_wage_ate"], R["ri_wage_ate_ci_low"], R["ri_wage_ate_ci_high"], wcell = ate_cell("Wage")141L["ri_wage_ate"] = loc(RI_ID, 2, "Wage", ATE, " | point estimate")142L["ri_wage_ate_ci_low"] = loc(RI_ID, 2, "Wage", ATE, " | 95% CI lower bound")143L["ri_wage_ate_ci_high"] = loc(RI_ID, 2, "Wage", ATE, " | 95% CI upper bound")144R["ri_wage_ate_se_reported"] = ("SE" in wcell.replace("SD", "")) or ("(" in wcell)145R["ri_wage_ate_se_derived"] = round((R["ri_wage_ate_ci_high"] - R["ri_wage_ate_ci_low"]) / (2 * Z), 4)146L["ri_wage_ate_se_derived"] = (f'{RI_ID} | Table 2 | row "Wage" | column "{ATE}" | derived: '147                               f'(CI upper - CI lower) / (2 x 1.96); SE not reported in {RI_ID}')148c, ln = t2["Wage"]; cc = c[h2.index("Control")]149print(f"  matched line {ln}: row 'Wage' col 'Control' -> '{cc}'")150R["ri_control_mean_wage"] = num(cc.split("(")[0]); L["ri_control_mean_wage"] = loc(RI_ID, 2, "Wage", "Control", " | mean (SD in parentheses not used)")151R["ri_reemployment_ate_pp"], *_ = ate_cell("Reemployment")152L["ri_reemployment_ate_pp"] = loc(RI_ID, 2, "Reemployment", ATE, " | point estimate (pp)")153R["ri_weeks_ui_ate"], R["ri_weeks_ui_ate_ci_low"], R["ri_weeks_ui_ate_ci_high"], _ = ate_cell("Weeks on UI")154L["ri_weeks_ui_ate"] = loc(RI_ID, 2, "Weeks on UI", ATE, " | point estimate")155L["ri_weeks_ui_ate_ci_low"] = loc(RI_ID, 2, "Weeks on UI", ATE, " | 95% CI lower bound")156L["ri_weeks_ui_ate_ci_high"] = loc(RI_ID, 2, "Weeks on UI", ATE, " | 95% CI upper bound")157158# ---------- cross-checks used to document data issues (not sources for keys) ----------159a19_se, _ = get(tA, hA, "ATT", "CS-DR", se=True)160a19_b, _ = get(tA, hA, "ATT", "CS-DR")161obs = [num(x) for x in t6["Observations"][0]]162treated = [num(x) for x in t6["Treated regions"][0]]163per640 = [o / (t + 640) for o, t in zip(obs, treated)]164per608 = [o / (t + 608) for o, t in zip(obs, treated)]165h3, t3 = ri_table(rl, "Outcome | Difference in means")166t3_wage = t3["Wage"][0][h3.index("Difference in means")]167wage_mid = (R["ri_wage_ate_ci_low"] + R["ri_wage_ate_ci_high"]) / 2168from statistics import NormalDist169p_implied = 2 * (1 - NormalDist().cdf(abs(R["ri_wage_ate"]) / R["ri_wage_ate_se_derived"]))170z_dcr = abs(b) / se171p_dcr = 2 * (1 - NormalDist().cdf(z_dcr))172z_hiv = abs(R["dcr_hiv_att_5km"]) / R["dcr_hiv_att_5km_se"]173174issues = [175    {"issue": f"Same 5 km CS-DR ATT ({a19_b}) has SE {t6["ATT"][1][h6.index("5 km")].strip("()")} in Table 6 but SE {tA["ATT"][1][hA.index("CS-DR")].strip("()")} in Table A19 of {DCR_ID}.",176     "handling": "Keys name Table 6, so the Table 6 SE (0.210) is used for dcr_att_5km_se and the derived CI; the discrepancy is reported, not resolved."},177    {"issue": (f"Control-region count inconsistent in {DCR_ID}: Tables 6/7 and Fig. 6 note say 608 never-treated regions, Table A19 note says 640. "178               f"Table 6 observations divided by (treated + 640) = {per640}; by (treated + 608) = {[round(x,3) for x in per608]}."),179     "handling": "dcr_obs_5km reported as printed (17250); the arithmetic suggests the panel uses 640 controls x 25 periods, but this is not asserted as fact."},180    {"issue": f"Narrative of {DCR_ID} is inconsistent with itself: abstract says fatal hospitalisations fall by 35%, Section 6.1 says ~30% at 5 km (Table 7 5 km ATT -0.354 -> exp(-0.354)-1 = {math.exp(-0.354)-1:.3f}); text calls HIV estimates 'close to zero' although 5 km ATT is -0.340 (not significant).",181     "handling": "Only table values are extracted; narrative claims are not used."},182    {"issue": f"{RI_ID} narrative reports the weeks-on-UI CI as unsigned magnitudes [1.75, 2.24] while Table 2 prints [-2.24, -1.75].",183     "handling": "Signed Table 2 values used, lower bound first."},184    {"issue": f"Rendering defect in {RI_ID} extract line 39: 'p ¡ 0.001' (should read p < 0.001).",185     "handling": "No extracted key affected; ignored."},186    {"issue": f"{RI_ID}: Table 2 labels the column 'Average treatment effect estimate' while the Table 3 caption calls the same numbers ITT estimates; Table 2 caption does not state the CI construction beyond 'methodology described in the main text' (omitted from the extract).",187     "handling": "Values taken from Table 2 as required; SE derivation assumes a symmetric normal-approximation interval (critical value 1.96), which is an assumption."},188]189190n_items = len(R)  # = 25: value keys assessed (13 DCR printed/marked, 2 DCR derived, 10 RESEA incl. 1 derived, 1 boolean)191out = dict(R)192out.update({193    "locators": L,194    "derivation_notes": {195        "dcr_att_5km_ci": "95% CI = ATT -/+ 1.96 x SE (normal approximation, critical value 1.96) using the printed Table 6 values ATT=-0.431, SE=0.210; rounded to 4 decimals. Table 6 does not print a CI. Sensitivity with critical value 2.0: see baseline_dcr_att_5km_ci_low_z2 / _high_z2.",196        "ri_wage_ate_se": "SE = (upper - lower) / (2 x 1.96) using the printed Table 2 95% CI [166, 2,141] (normal approximation, critical value 1.96, assumes a symmetric Wald-type interval); rounded to 4 decimals. The source prints no SE.",197    },198    "task_id": "F4-conf-01",199    "feasibility": "complete",200    "feasibility_reason": None,201    "data_issues": issues,202    "n_input_rows": {"README.md": wc_l(README), DCR_ID + ".txt": wc_l(DCR), RI_ID + ".txt": wc_l(RI)},203    "n_analysed": n_items,204    "random_seed": None,205    "baseline_dcr_att_5km_ci_low_z2": round(b - 2.0 * se, 4),206    "baseline_dcr_att_5km_ci_high_z2": round(b + 2.0 * se, 4),207    "baseline_ri_wage_ate_se_derived_z2": round((R["ri_wage_ate_ci_high"] - R["ri_wage_ate_ci_low"]) / 4.0, 4),208    "check_ri_wage_ci_midpoint": wage_mid,209    "check_ri_wage_p_implied_by_derived_se": round(p_implied, 4),210    "check_dcr_att_5km_p_implied_normal": round(p_dcr, 4),211    "check_dcr_hiv_5km_abs_z": round(z_hiv, 4),212    "check_dcr_a19_se_5km": a19_se,213    "check_table3_wage_identical_to_table2": t3_wage == wcell,214})215# Printed literals (to preserve trailing zeros such as 0.210 in the JSON number text)216PRINTED = {217    "dcr_att_5km": lit(t6["ATT"][0][h6.index("5 km")]), "dcr_att_5km_se": lit(t6["ATT"][1][h6.index("5 km")]),218    "dcr_att_3km": lit(t6["ATT"][0][h6.index("3 km")]), "dcr_att_3km_se": lit(t6["ATT"][1][h6.index("3 km")]),219    "dcr_obs_5km": lit(t6["Observations"][0][h6.index("5 km")]),220    "dcr_narcotics_att_3km": lit(t7["Narcotics"][0][h7.index("3 km")]), "dcr_narcotics_att_3km_se": lit(t7["Narcotics"][1][h7.index("3 km")]),221    "dcr_fatal_att_1km": lit(t7["Drug-related (fatal)"][0][h7.index("1 km")]), "dcr_fatal_att_1km_se": lit(t7["Drug-related (fatal)"][1][h7.index("1 km")]),222    "dcr_hiv_att_5km": lit(t7["HIV"][0][h7.index("5 km")]), "dcr_hiv_att_5km_se": lit(t7["HIV"][1][h7.index("5 km")]),223    "ri_wage_ate": lit(wcell.split(";")[0]), "ri_control_mean_wage": lit(cc.split("(")[0]),224    "ri_reemployment_ate_pp": lit(t2["Reemployment"][0][h2.index(ATE)].split(";")[0]),225    "ri_weeks_ui_ate": lit(t2["Weeks on UI"][0][h2.index(ATE)].split(";")[0]),226}227for k, v in PRINTED.items():228    assert float(v) == out[k], (k, v, out[k])229os.makedirs(os.path.join(ROOT, "out"), exist_ok=True)230with open(os.path.join(ROOT, "out", "results.json"), "w") as f:231    txt = json.dumps(out, indent=2, ensure_ascii=False)232    for k, v in PRINTED.items():233        txt, n = re.subn(r'^(  "%s": )[^,\n]+' % re.escape(k), lambda m: m.group(1) + v, txt, flags=re.M)234        assert n == 1, k235    assert json.loads(txt) == json.loads(json.dumps(out, ensure_ascii=False))236    f.write(txt + "\n")237print("Wrote out/results.json with", len(R), "value keys")238
Files · main
Menu

Theme