"""F4-dev-01: extract headline LFS figures and assess an assertion from the StatCan Daily extract. Reads only data/; writes out/results.json deterministically. No randomness.""" import json, re, os, hashlib ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SRC_ID = "statcan-daily-lfs-2026-09" SRC = os.path.join(ROOT, "data", "sources", SRC_ID + ".txt") README = os.path.join(ROOT, "data", "README.md") def wc_l(p): with open(p, "rb") as f: return f.read().count(b"\n") lines = open(SRC, encoding="utf-8").read().split("\n") # map each line to the most recent '## ' section heading (not '###') sections, cur = [], None for ln in lines: m = re.match(r"^##\s+(.*\S)\s*$", ln) if m and not ln.startswith("###"): cur = m.group(1) sections.append(cur) def find(pattern, section=None): return [(i, lines[i], sections[i]) for i in range(len(lines)) if re.search(pattern, lines[i]) and (section is None or sections[i] == section)] def loc(sec): return f'{SRC_ID} | section "{sec}"' # (a1) unemployment rate, Highlights pat_u = r"The unemployment rate (?:increased|rose|fell|decreased|was little changed) [0-9.]+ percentage points to (\d+\.\d)%" hits_u = find(pat_u, "Highlights") assert hits_u, "unemployment rate not found in Highlights" i, txt, sec = hits_u[0] unemp = float(re.search(pat_u, txt).group(1)) print(f"[unemployment] line {i+1} ({sec}): {txt}") SEC_U2 = "Unemployment rate increases to 6.5%" h2 = find(r"The unemployment rate rose [\d.]+ percentage points to (\d+\.\d)% in September", SEC_U2) assert h2 unemp2 = float(re.search(r"to (\d+\.\d)% in September", h2[0][1]).group(1)) print(f" cross-check line {h2[0][0]+1} ({SEC_U2}): {h2[0][1][:100]}") # (a2) employment change, Highlights pat_e = r"Employment (declined|increased|rose|fell) by ([\d,]+) \(([-+]?\d+\.\d)%\) in September" hits_e = find(pat_e, "Highlights") assert hits_e, "employment change not found in Highlights" i, txt, sec = hits_e[0] m = re.search(pat_e, txt) sign = -1 if m.group(1) in ("declined", "fell") else 1 emp = sign * int(m.group(2).replace(",", "")) print(f"[employment] line {i+1} ({sec}): {txt}") SEC_E2 = "Employment falls for second consecutive month" e2 = find(r"employment (declined|fell) by ([\d,]+) \(", SEC_E2) assert e2 m2 = re.search(r"employment (declined|fell) by ([\d,]+) \(", e2[0][1]) emp2 = -int(m2.group(2).replace(",", "")) print(f" cross-check line {e2[0][0]+1} ({SEC_E2}): {e2[0][1][:100]}") # (b) assertion SEC_B = "Unemployment rate up for core-aged women" w = find(r"core-aged women \(25 to 54 years old\), the unemployment rate (rose|fell) (\d+\.\d) percentage points to (\d+\.\d)%", SEC_B) mm = find(r"unemployment rate for core-aged men (rose|fell) (\d+\.\d) percentage points to (\d+\.\d)%", SEC_B) assert w and mm wd = re.search(r"unemployment rate (rose|fell) (\d+\.\d) percentage points to (\d+\.\d)%", w[0][1]).groups() md = re.search(r"core-aged men (rose|fell) (\d+\.\d) percentage points to (\d+\.\d)%", mm[0][1]).groups() print(f"[assertion women] line {w[0][0]+1} ({SEC_B}): {w[0][1][:160]}") print(f"[assertion men] line {mm[0][0]+1} ({SEC_B}): {mm[0][1]}") women_ok = tuple(wd) == ("rose", "0.3", "5.3") men_ok = tuple(md) == ("rose", "0.2", "5.8") if women_ok and men_ok: verdict, reason = "supported", "The release states both elements as asserted." else: verdict = "not_supported" reason = (f"The women's element matches (\"the unemployment rate {wd[0]} {wd[1]} percentage points to {wd[2]}%\") " f"but the release states \"the unemployment rate for core-aged men {md[0]} {md[1]} percentage points to {md[2]}%\", " f"contradicting the claim that the men's rate rose.") print(f"[verdict] {verdict}: women_match={women_ok}, men_match={men_ok}") results = { "task_id": "F4-dev-01", "unemployment_rate_sep2026": unemp, "employment_change_sep2026_persons": emp, "locators": { "unemployment_rate_sep2026": loc(hits_u[0][2]), "employment_change_sep2026_persons": loc(hits_e[0][2]), }, "assertion_verdict": verdict, "assertion_locator": loc(SEC_B), "assertion_reason": reason, "feasibility": "complete", "feasibility_reason": None, "data_issues": [ {"issue": "data/README.md mentions re-set tables ('RESEA Tables 2-3, London Table 3 header') and regression-table conventions that do not correspond to anything in this extract (apparently shared template text).", "handling": "Ignored as irrelevant to this extract, which contains no tables; extract SHA-256 verified against README."}, {"issue": "Data tables and charts are not reproduced in the extract (headings only).", "handling": "All values taken from the narrative text; no table values were needed."}, {"issue": "Note-to-readers sections use '###' headings rather than '##'.", "handling": "Only '##' headings are used as locators; no extracted figure lies under a '###' heading."}, ], "n_input_rows": {"README.md": wc_l(README), SRC_ID + ".txt": wc_l(SRC)}, "n_analysed": 3, "random_seed": None, "source_sha256": hashlib.sha256(open(SRC, "rb").read()).hexdigest(), "baseline_unemployment_rate_dedicated_section": unemp2, "baseline_employment_change_dedicated_section": emp2, "headline_crosscheck_agree": bool(unemp == unemp2 and emp == emp2), } with open(os.path.join(ROOT, "out", "results.json"), "w") as f: json.dump(results, f, indent=2, sort_keys=True) print("wrote out/results.json")