"""F4-dev-01: extract headline LFS figures and assess one assertion from the extract. Run from workspace root: ~/wp29/venv/bin/python out/analysis.py""" import json, re, hashlib from pathlib import Path SRC_ID = "statcan-daily-lfs-2026-09" SRC = Path("data/sources") / f"{SRC_ID}.txt" README = Path("data/README.md") EXPECTED_SHA = "7ea5fec57c87874ff5ee09a8304927a1aaed5a1837db5312fab9f7a2d5f2d0bb" raw = SRC.read_bytes() sha = hashlib.sha256(raw).hexdigest() lines = raw.decode("utf-8").splitlines() # map each line to the most recent '## ' / '### ' section heading sections = [] cur = None for ln in lines: m = re.match(r"^#{2,3}\s+(.*?)\s*$", ln) if m: cur = m.group(1).strip() sections.append(cur) def find(pattern, section=None): out = [] for i, ln in enumerate(lines): if (section is None or sections[i] == section): m = re.search(pattern, ln) if m: out.append((i + 1, sections[i], m, ln)) return out def loc(sec): return f'{SRC_ID} | section "{sec}"' # (a1) unemployment rate ur_hits = find(r"[Tt]he unemployment rate (?:increased|rose) ([\d.]+) percentage points to ([\d.]+)%", "Highlights") print("Unemployment rate matches (Highlights):") for h in ur_hits: print(f" line {h[0]} [{h[1]}]: {h[3]}") ur = float(ur_hits[0][2].group(2)) ur_check = find(r"The unemployment rate rose [\d.]+ percentage points to ([\d.]+)% in September", "Unemployment rate increases to 6.5%") for h in ur_check: print(f" cross-check line {h[0]} [{h[1]}]: {h[3]}") assert all(float(h[2].group(1)) == ur for h in ur_check) # (a2) employment change emp_hits = find(r"Employment (declined|increased|rose|fell) by ([\d,]+) \(([-+][\d.]+)%\) in September", "Highlights") print("Employment change matches (Highlights):") for h in emp_hits: print(f" line {h[0]} [{h[1]}]: {h[3]}") verb, num = emp_hits[0][2].group(1), emp_hits[0][2].group(2) sign = -1 if verb in ("declined", "fell") else 1 emp = sign * int(num.replace(",", "")) emp_check = find(r"employment (declined|increased) by ([\d,]+) \(", "Employment falls for second consecutive month") for h in emp_check: print(f" cross-check line {h[0]} [{h[1]}]: {h[3]}") assert all((-1 if h[2].group(1) == "declined" else 1) * int(h[2].group(2).replace(",", "")) == emp for h in emp_check) # (b) assertion ASSERT_SEC = "Unemployment rate up for core-aged women" w = find(r"core-aged women \(25 to 54 years old\), the unemployment rate (rose|fell) ([\d.]+) percentage points to ([\d.]+)%", ASSERT_SEC) m_ = find(r"unemployment rate for core-aged men (rose|fell|increased|decreased) ([\d.]+) percentage points to ([\d.]+)%", ASSERT_SEC) print("Assertion evidence:") for h in w + m_: print(f" line {h[0]} [{h[1]}]: {h[3]}") wd, wc, wl = w[0][2].group(1), float(w[0][2].group(2)), float(w[0][2].group(3)) md, mc, ml = m_[0][2].group(1), float(m_[0][2].group(2)), float(m_[0][2].group(3)) checks = { "women direction rose": wd == "rose", "women change 0.3": wc == 0.3, "women level 5.3": wl == 5.3, "men direction rose": md in ("rose", "increased"), "men change 0.2": mc == 0.2, "men level 5.8": ml == 5.8, } print("Element checks:", checks) verdict = "supported" if all(checks.values()) else "not_supported" reason = (f'The release confirms the women\'s figures (rose {wc} points to {wl}%) and the men\'s magnitude and level, ' f'but states that "the unemployment rate for core-aged men {md} {mc} percentage points to {ml}%", ' f'contradicting the assertion that it rose.') if verdict == "not_supported" else \ "Every element of the assertion is stated in the release." data_issues = [ {"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).", "handling": "Only narrative text used; figures taken from sentences; trailing whitespace stripped from headings when building locators."}, {"issue": "The 'Sustainable Development Goals' section ends with 'the following goals:' but the list of goals is missing from the extract.", "handling": "Not relevant to the estimands; noted only."}, {"issue": "Headline figures are stated in more than one section (Highlights and a thematic section).", "handling": "Locator gives the Highlights section; the script cross-checks that the thematic-section values are identical (they are)."}, ] if sha != EXPECTED_SHA: data_issues.append({"issue": f"SHA-256 of extract ({sha}) differs from README", "handling": "Reported; proceeded."}) def wc_l(p): return p.read_bytes().count(b"\n") results = { "unemployment_rate_sep2026": ur, "employment_change_sep2026_persons": emp, "locators": { "unemployment_rate_sep2026": loc(ur_hits[0][1]), "employment_change_sep2026_persons": loc(emp_hits[0][1]), }, "assertion_verdict": verdict, "assertion_locator": loc(ASSERT_SEC), "assertion_reason": reason, "task_id": "F4-dev-01", "feasibility": "complete", "feasibility_reason": None, "data_issues": data_issues, "n_input_rows": {"README.md": wc_l(README), f"sources/{SRC_ID}.txt": wc_l(SRC)}, "n_analysed": 3, "random_seed": None, } Path("out/results.json").write_text(json.dumps(results, indent=2, ensure_ascii=False) + "\n") print(json.dumps(results, indent=2, ensure_ascii=False))