1"""F4-dev-01: extract headline LFS figures and assess one assertion from the extract.2Run from workspace root: ~/wp29/venv/bin/python out/analysis.py"""3import json, re, hashlib4from pathlib import Path56SRC_ID = "statcan-daily-lfs-2026-09"7SRC = Path("data/sources") / f"{SRC_ID}.txt"8README = Path("data/README.md")9EXPECTED_SHA = "7ea5fec57c87874ff5ee09a8304927a1aaed5a1837db5312fab9f7a2d5f2d0bb"1011raw = SRC.read_bytes()12sha = hashlib.sha256(raw).hexdigest()13lines = raw.decode("utf-8").splitlines()1415# map each line to the most recent '## ' / '### ' section heading16sections = []17cur = None18for ln in lines:19 m = re.match(r"^#{2,3}\s+(.*?)\s*$", ln)20 if m:21 cur = m.group(1).strip()22 sections.append(cur)2324def find(pattern, section=None):25 out = []26 for i, ln in enumerate(lines):27 if (section is None or sections[i] == section):28 m = re.search(pattern, ln)29 if m:30 out.append((i + 1, sections[i], m, ln))31 return out3233def loc(sec):34 return f'{SRC_ID} | section "{sec}"'3536# (a1) unemployment rate37ur_hits = find(r"[Tt]he unemployment rate (?:increased|rose) ([\d.]+) percentage points to ([\d.]+)%", "Highlights")38print("Unemployment rate matches (Highlights):")39for h in ur_hits: print(f" line {h[0]} [{h[1]}]: {h[3]}")40ur = float(ur_hits[0][2].group(2))41ur_check = find(r"The unemployment rate rose [\d.]+ percentage points to ([\d.]+)% in September", "Unemployment rate increases to 6.5%")42for h in ur_check: print(f" cross-check line {h[0]} [{h[1]}]: {h[3]}")43assert all(float(h[2].group(1)) == ur for h in ur_check)4445# (a2) employment change46emp_hits = find(r"Employment (declined|increased|rose|fell) by ([\d,]+) \(([-+][\d.]+)%\) in September", "Highlights")47print("Employment change matches (Highlights):")48for h in emp_hits: print(f" line {h[0]} [{h[1]}]: {h[3]}")49verb, num = emp_hits[0][2].group(1), emp_hits[0][2].group(2)50sign = -1 if verb in ("declined", "fell") else 151emp = sign * int(num.replace(",", ""))52emp_check = find(r"employment (declined|increased) by ([\d,]+) \(", "Employment falls for second consecutive month")53for h in emp_check: print(f" cross-check line {h[0]} [{h[1]}]: {h[3]}")54assert all((-1 if h[2].group(1) == "declined" else 1) * int(h[2].group(2).replace(",", "")) == emp for h in emp_check)5556# (b) assertion57ASSERT_SEC = "Unemployment rate up for core-aged women"58w = find(r"core-aged women \(25 to 54 years old\), the unemployment rate (rose|fell) ([\d.]+) percentage points to ([\d.]+)%", ASSERT_SEC)59m_ = find(r"unemployment rate for core-aged men (rose|fell|increased|decreased) ([\d.]+) percentage points to ([\d.]+)%", ASSERT_SEC)60print("Assertion evidence:")61for h in w + m_: print(f" line {h[0]} [{h[1]}]: {h[3]}")62wd, wc, wl = w[0][2].group(1), float(w[0][2].group(2)), float(w[0][2].group(3))63md, mc, ml = m_[0][2].group(1), float(m_[0][2].group(2)), float(m_[0][2].group(3))64checks = {65 "women direction rose": wd == "rose", "women change 0.3": wc == 0.3, "women level 5.3": wl == 5.3,66 "men direction rose": md in ("rose", "increased"), "men change 0.2": mc == 0.2, "men level 5.8": ml == 5.8,67}68print("Element checks:", checks)69verdict = "supported" if all(checks.values()) else "not_supported"70reason = (f'The release confirms the women\'s figures (rose {wc} points to {wl}%) and the men\'s magnitude and level, '71 f'but states that "the unemployment rate for core-aged men {md} {mc} percentage points to {ml}%", '72 f'contradicting the assertion that it rose.') if verdict == "not_supported" else \73 "Every element of the assertion is stated in the release."7475data_issues = [76 {"issue": "Chart, infographic, map and data tables (Tables 1-12) are not reproduced in the extract; only their headings remain (e.g. empty '## Chart 1 ' headings with trailing spaces).",77 "handling": "Only narrative text used; figures taken from sentences; trailing whitespace stripped from headings when building locators."},78 {"issue": "The 'Sustainable Development Goals' section ends with 'the following goals:' but the list of goals is missing from the extract.",79 "handling": "Not relevant to the estimands; noted only."},80 {"issue": "Headline figures are stated in more than one section (Highlights and a thematic section).",81 "handling": "Locator gives the Highlights section; the script cross-checks that the thematic-section values are identical (they are)."},82]83if sha != EXPECTED_SHA:84 data_issues.append({"issue": f"SHA-256 of extract ({sha}) differs from README", "handling": "Reported; proceeded."})8586def wc_l(p): return p.read_bytes().count(b"\n")8788results = {89 "unemployment_rate_sep2026": ur,90 "employment_change_sep2026_persons": emp,91 "locators": {92 "unemployment_rate_sep2026": loc(ur_hits[0][1]),93 "employment_change_sep2026_persons": loc(emp_hits[0][1]),94 },95 "assertion_verdict": verdict,96 "assertion_locator": loc(ASSERT_SEC),97 "assertion_reason": reason,98 "task_id": "F4-dev-01",99 "feasibility": "complete",100 "feasibility_reason": None,101 "data_issues": data_issues,102 "n_input_rows": {"README.md": wc_l(README), f"sources/{SRC_ID}.txt": wc_l(SRC)},103 "n_analysed": 3,104 "random_seed": None,105}106Path("out/results.json").write_text(json.dumps(results, indent=2, ensure_ascii=False) + "\n")107print(json.dumps(results, indent=2, ensure_ascii=False))108