From f5bd1d581e3d5160d4a483435ad91ddd178eb9f8 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 16:08:12 -0400 Subject: [PATCH 1/3] Test that a fact's period is the tax year its source table names soi-w2-statistics-2020 reads one workbook, IRS SOI Table 4.B for Tax Year 2020. Until #292 it rendered its labels from --year, and the microcosm US feed built it at --year 2023: five TY2020 cells went out as ty2023 facts. #292 made the labels literal and added the artifact-year restamp guard, which compares one build with another. A literal label that is simply the wrong year passes that guard. These tests compare the labels with what the publisher printed. For every pinned package whose artifact.source_table names exactly one tax year, each tax-year fact built at the artifact year must carry that year as its period, in its tax_year_ vintage and in its record ids. For the W-2 package they also check that the declared title is the workbook's own A1 cell, that the five facts equal a direct read of the sheet, and that every --year from 2019 to 2025 builds the same TY2020 facts. Tax Year 2020 is the latest Table 4.B IRS has published (checked 2026-10-10: 21in04w2all.xlsx through 24in04w2all.xlsx return 404), so there is no later table to add. Co-Authored-By: Claude Opus 5.5 --- docs/agent-source-package-harness.md | 8 + tests/test_chronicle_source_table_year.py | 241 ++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 tests/test_chronicle_source_table_year.py diff --git a/docs/agent-source-package-harness.md b/docs/agent-source-package-harness.md index c8cec774..90e4e622 100644 --- a/docs/agent-source-package-harness.md +++ b/docs/agent-source-package-harness.md @@ -864,6 +864,14 @@ year's column, sheet or rows. A year the file does not cover fails with its own error, such as `No source artifact for year 2027` or a selected-row mismatch. +That check compares one build with another, so it cannot see a literal label +that is simply the wrong year. `tests/test_chronicle_source_table_year.py` +covers that case for every pinned package whose `artifact.source_table` names +exactly one tax year ("…, Tax Year 2020"): each tax-year fact built at the +artifact year must carry that year as its period, in its `tax_year_` +vintage and in its record ids. State the table title as the publisher printed +it, with its year, so the test has something to check. + Agents may add new package directories and YAML specs. They should not modify `chronicle.core`, `chronicle.database`, or `chronicle.suite` unless the package cannot be expressed in the current contract and the failure is documented in the build diff --git a/tests/test_chronicle_source_table_year.py b/tests/test_chronicle_source_table_year.py new file mode 100644 index 00000000..7828babd --- /dev/null +++ b/tests/test_chronicle_source_table_year.py @@ -0,0 +1,241 @@ +"""A fact's period is the tax year its source table says it is for. + +IRS SOI titles each table with the tax year it covers ("..., Tax Year 2020"), +and a package states that title as ``artifact.source_table``. The artifact-year +restamp guard (``test_chronicle_artifact_year_restamp.py``) refuses a pinned +package whose labels follow ``--year``. It compares one build with another, so +it cannot see a package whose labels are simply the wrong year. These tests +compare the labels with what the publisher printed. + +Invariant under test: when a package's source table title names exactly one +tax year, every fact the package builds at its artifact year has that year as +its period, in its ``tax_year_`` vintage and in its record ids. + +Origin: until PolicyEngine/chronicle#292, ``soi-w2-statistics-2020`` rendered +its labels from ``--year``, and the microcosm US feed built it at ``--year +2023``. Five cells of IRS SOI Table 4.B for Tax Year 2020 went out as ty2023 +facts, and microcosm aged the Box 7 tips total one year to 2024 where it needed +four. +""" + +from __future__ import annotations + +import itertools +import json +import re +from pathlib import Path + +import openpyxl +import pytest +import yaml + +from chronicle.source_package import ( + SOURCE_PACKAGE_FILENAME, + artifact_year_restamp_issues, + discover_source_package_dirs, + load_source_package, +) +from chronicle.store import fact_to_mapping + +REPO_ROOT = Path(__file__).resolve().parents[1] + +_YEAR = r"(? int | None: + """The one tax year a source table title names, else ``None``. + + ``None`` covers a title that names no tax year and a title that names more + than one: neither says which year a cell of the table is for. + """ + years = { + int(year) + for span in _TAX_YEAR_SPAN.findall(title) + for year in re.findall(_YEAR, span) + } + return years.pop() if len(years) == 1 else None + + +# --- the title reader -------------------------------------------------------- + +W2_TITLE = ( + "Table 4.B. Summary of Items for Taxpayers with Form W-2, by Return and " + "Earner Type, Tax Year 2020" +) + + +@pytest.mark.parametrize( + ("title", "expected"), + [ + (W2_TITLE, 2020), + ("Table 4.B, Tax Year 2020 (revised January 2026)", 2020), + ("Table 1, Tax Year 2020, and Table 2, Tax Year 2020", 2020), + ("table 1.4, tax year\n2023", 2023), + ("Individual returns, Tax Years 2019 and 2020", None), + ("Individual returns, Tax Years 2019-2020", None), + ("Individual returns, tax years 2019, 2020 and 2021", None), + ("Individual returns, Tax Year 2020 through 2022", None), + ("Table 1, Tax Year 2019, and Table 2, Tax Year 2020", None), + ("SPI Tables 3.1 to 3.11, tax year 2023-24", None), + ("SPI Tables 3.1 to 3.11, tax year 2023/24", None), + ("Tax Year 20201", None), + ("Estimates of Federal Tax Expenditures for Fiscal Years 2024-2028", None), + ("Employer-Sponsored Insurance: Calendar Years 1987-2024", None), + ("2020 Form W-2 statistics", None), + ("", None), + ], +) +def test_title_tax_year_reads_publisher_titles(title, expected): + assert title_tax_year(title) == expected + + +PREFIXES = ("", "Table 4.B. Items for Taxpayers with Form W-2, ", "2020 release: ") +SUFFIXES = ("", ".", " (revised)", " [1]", ", all returns") +CONNECTORS = (" and ", "-", "–", " to ", " through ", ", ", " & ") +YEARS = (1999, 2019, 2020, 2023, 2099) + + +def test_title_tax_year_over_every_synthetic_title(): + """One year reads back; two different years never read as one.""" + for prefix, suffix, year in itertools.product(PREFIXES, SUFFIXES, YEARS): + assert title_tax_year(f"{prefix}Tax Year {year}{suffix}") == year + assert title_tax_year(f"{prefix}tax year {year}{suffix}") == year + # No "Tax Year" marker: a year elsewhere in the title is not a tax year. + assert title_tax_year(f"{prefix}Calendar Year {year}{suffix}") is None + for prefix, suffix, connector, first, second in itertools.product( + PREFIXES, SUFFIXES, CONNECTORS, YEARS, YEARS + ): + title = f"{prefix}Tax Years {first}{connector}{second}{suffix}" + assert title_tax_year(title) == (first if first == second else None), title + + +# --- the real package tree --------------------------------------------------- + + +def _single_tax_year_packages() -> dict[str, int]: + """Pinned packages whose source table title names exactly one tax year.""" + packages = {} + for sub in discover_source_package_dirs(REPO_ROOT / "packages"): + path = REPO_ROOT / "packages" / sub / SOURCE_PACKAGE_FILENAME + text = path.read_text(encoding="utf-8") + if "artifact_year:" not in text or not re.search(r"(?i)tax\s+year", text): + continue + artifact = yaml.safe_load(text).get("artifact") or {} + year = title_tax_year(str(artifact.get("source_table") or "")) + if year is not None: + packages[str(sub)] = year + return dict(sorted(packages.items())) + + +SINGLE_TAX_YEAR_PACKAGES = _single_tax_year_packages() + + +def test_real_tree_has_a_single_tax_year_title_to_check(): + # Guards the parametrization below against silently checking nothing. + assert SINGLE_TAX_YEAR_PACKAGES.get("irs_soi/w2_statistics_2020") == 2020 + + +@pytest.mark.parametrize("package_path", sorted(SINGLE_TAX_YEAR_PACKAGES)) +def test_facts_carry_the_tax_year_their_source_table_names(package_path): + package = load_source_package(REPO_ROOT / "packages" / package_path) + table_year = SINGLE_TAX_YEAR_PACKAGES[package_path] + facts = [ + fact_to_mapping(fact) + for fact in package.build_facts(package.artifact.artifact_year) + ] + assert facts, package_path + for fact in facts: + where = (package_path, fact["source_record_id"]) + assert title_tax_year(fact["source"]["source_table"]) == table_year, where + if fact["period"]["type"] != "tax_year": + continue + assert fact["period"]["value"] == table_year, where + vintage = _TAX_YEAR_VINTAGE.fullmatch(fact["source"]["vintage"]) + if vintage is not None: + assert int(vintage.group(1)) == table_year, where + for identifier in (fact["source_record_id"], fact["layout"]["record_set_id"]): + stamped = {int(year) for year in _TAX_YEAR_ID_TOKEN.findall(identifier)} + assert stamped <= {table_year}, (where, identifier) + + +# --- IRS SOI Table 4.B (Form W-2 items) -------------------------------------- + +W2_PACKAGE = "irs_soi/w2_statistics_2020" +W2_WORKBOOK = ( + REPO_ROOT / "db" / "data" / "irs_soi" / "w2_statistics" / "20in04w2all.xlsx" +) +# (record-set slug, row, measure id, column, scale) for every cell the package +# reads from sheet "Table 4.B"; money amounts are in thousands of dollars. +W2_CELLS = ( + ("form_w2_social_security_tips", 13, "return_count", "B", 1), + ("form_w2_social_security_tips", 13, "taxpayer_count", "C", 1), + ("form_w2_social_security_tips", 13, "amount", "D", 1000), + ("form_w2_401k_elective_deferrals", 22, "amount", "D", 1000), + ("form_w2_designated_roth_401k_contributions", 41, "amount", "D", 1000), +) +# What IRS printed, read from the workbook on 2026-10-10. +W2_PUBLISHED = { + "irs_soi.ty2020.form_w2_social_security_tips.box_7_social_security_tips.return_count": 6_038_613, + "irs_soi.ty2020.form_w2_social_security_tips.box_7_social_security_tips.taxpayer_count": 6_105_713, + "irs_soi.ty2020.form_w2_social_security_tips.box_7_social_security_tips.amount": 26_786_522_000, + "irs_soi.ty2020.form_w2_401k_elective_deferrals.box_12_d_401k_elective_deferrals.amount": 277_859_181_000, + "irs_soi.ty2020.form_w2_designated_roth_401k_contributions.box_12_aa_designated_roth_401k_contributions.amount": 32_302_509_000, +} + + +def _w2_facts(year: int) -> list[dict]: + package = load_source_package(REPO_ROOT / "packages" / W2_PACKAGE) + return [fact_to_mapping(fact) for fact in package.build_facts(year)] + + +def test_w2_package_title_is_the_workbooks_own_title_cell(): + """The declared title is the bytes' title, so the year check reads IRS.""" + sheet = openpyxl.load_workbook(W2_WORKBOOK, read_only=True)["Table 4.B"] + printed = sheet["A1"].value + package = load_source_package(REPO_ROOT / "packages" / W2_PACKAGE) + assert printed == package.artifact.source_table == W2_TITLE + assert title_tax_year(printed) == 2020 == package.artifact.artifact_year + + +def test_w2_facts_are_the_published_tax_year_2020_cells(): + """Differential: the package's facts against a direct read of the sheet.""" + sheet = openpyxl.load_workbook(W2_WORKBOOK, read_only=True)["Table 4.B"] + facts = {fact["source_record_id"]: fact for fact in _w2_facts(2020)} + assert {key: fact["value"] for key, fact in facts.items()} == W2_PUBLISHED + read_directly = {} + for record_set, row, measure, column, scale in W2_CELLS: + (key,) = ( + key + for key in facts + if f".{record_set}." in key and key.endswith(f".{measure}") + ) + read_directly[key] = sheet[f"{column}{row}"].value * scale + assert read_directly == W2_PUBLISHED + for fact in facts.values(): + assert fact["period"] == {"type": "tax_year", "value": 2020} + assert fact["source"]["vintage"] == "tax_year_2020" + assert fact["source"]["source_table"] == W2_TITLE + assert fact["source"]["source_file"] == "20in04w2all.xlsx" + + +@pytest.mark.parametrize("year", [2019, 2021, 2022, 2023, 2024, 2025]) +def test_w2_package_builds_the_same_tax_year_2020_facts_at_every_year(year): + """``--year`` never relabels the table: 2023 is the year microcosm built.""" + package = load_source_package(REPO_ROOT / "packages" / W2_PACKAGE) + assert artifact_year_restamp_issues(package, year) == [] + + def serialized(facts): + return [json.dumps(fact, sort_keys=True, default=str) for fact in facts] + + assert serialized(_w2_facts(year)) == serialized(_w2_facts(2020)) From 4e96e6e30f861b4d61ef11119c2c45e631eff42b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 17:33:03 -0400 Subject: [PATCH 2/3] Close the review's gaps in the source-table year tests - Check the legal vintage too: a wrong literal legal_vintage on the W-2 tips amount passed every test, because it is the same at every build year. The registry test now reads any tax_year_ in the source vintage or the legal vintage, and the W-2 test pins the tips amount's legal vintage. - Require the table's year to be the only ty token in each record id and record set id; an id with no year token used to pass. - Choose packages from the decoded artifact block, not from the YAML text, so a quoted key or an escaped title cannot hide one. Only the part before the top-level record_sets key is parsed (all 329 packages in about 2 s, where a full parse takes about 50 s). - Make the title reader's year list possessive and refuse any separator followed by a digit after it. "Tax Years 2019 and 2020/21" used to read as 2019 by giving back the years the lookahead refused, and "2020; 2021" and "2020 to 21" read as 2020. ";" and "/" now separate full years. - Say in the harness doc exactly what the test enforces. Co-Authored-By: Claude Opus 5.5 --- docs/agent-source-package-harness.md | 11 ++- tests/test_chronicle_source_table_year.py | 100 ++++++++++++++++++---- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/docs/agent-source-package-harness.md b/docs/agent-source-package-harness.md index 90e4e622..aba943f7 100644 --- a/docs/agent-source-package-harness.md +++ b/docs/agent-source-package-harness.md @@ -867,10 +867,13 @@ mismatch. That check compares one build with another, so it cannot see a literal label that is simply the wrong year. `tests/test_chronicle_source_table_year.py` covers that case for every pinned package whose `artifact.source_table` names -exactly one tax year ("…, Tax Year 2020"): each tax-year fact built at the -artifact year must carry that year as its period, in its `tax_year_` -vintage and in its record ids. State the table title as the publisher printed -it, with its year, so the test has something to check. +exactly one tax year ("…, Tax Year 2020"). Each tax-year fact built at the +artifact year must have that year as its period. Any `tax_year_` in its +source vintage or legal vintage must name that year. It must also be the only +`ty` token in the fact's record id and record set id. A title that names +a range or abbreviates a year ("tax year 2023-24") is not read as one tax year +and is not checked. State the table title as the publisher printed it, with +its year, so the test has something to check. Agents may add new package directories and YAML specs. They should not modify `chronicle.core`, `chronicle.database`, or `chronicle.suite` unless the package cannot be diff --git a/tests/test_chronicle_source_table_year.py b/tests/test_chronicle_source_table_year.py index 7828babd..7ff05e2c 100644 --- a/tests/test_chronicle_source_table_year.py +++ b/tests/test_chronicle_source_table_year.py @@ -8,8 +8,10 @@ compare the labels with what the publisher printed. Invariant under test: when a package's source table title names exactly one -tax year, every fact the package builds at its artifact year has that year as -its period, in its ``tax_year_`` vintage and in its record ids. +tax year, every tax-year fact the package builds at its artifact year has that +year as its period, in its source vintage and legal vintage when they name a +``tax_year_``, and as the only ``ty`` token of its record id and +record set id. Origin: until PolicyEngine/chronicle#292, ``soi-w2-statistics-2020`` rendered its labels from ``--year``, and the microcosm US feed built it at ``--year @@ -27,7 +29,6 @@ import openpyxl import pytest -import yaml from chronicle.source_package import ( SOURCE_PACKAGE_FILENAME, @@ -36,20 +37,23 @@ load_source_package, ) from chronicle.store import fact_to_mapping +from chronicle.yaml_io import safe_load REPO_ROOT = Path(__file__).resolve().parents[1] _YEAR = r"(? int | None: ("Table 1, Tax Year 2019, and Table 2, Tax Year 2020", None), ("SPI Tables 3.1 to 3.11, tax year 2023-24", None), ("SPI Tables 3.1 to 3.11, tax year 2023/24", None), + ("Individual returns, Tax Years 2019 and 2020/21", None), + ("Individual returns, Tax Year 2020 and 2021/22", None), + ("Individual returns, Tax Years 2020; 2021", None), + ("Individual returns, Tax Year 2020 to 21", None), + ("Individual returns, Tax Years 2020; 2020", 2020), ("Tax Year 20201", None), ("Estimates of Federal Tax Expenditures for Fiscal Years 2024-2028", None), ("Employer-Sponsored Insurance: Calendar Years 1987-2024", None), @@ -102,8 +111,9 @@ def test_title_tax_year_reads_publisher_titles(title, expected): PREFIXES = ("", "Table 4.B. Items for Taxpayers with Form W-2, ", "2020 release: ") SUFFIXES = ("", ".", " (revised)", " [1]", ", all returns") -CONNECTORS = (" and ", "-", "–", " to ", " through ", ", ", " & ") +CONNECTORS = (" and ", "-", "–", " to ", " through ", ", ", " & ", "; ", "/") YEARS = (1999, 2019, 2020, 2023, 2099) +INCOMPLETE_TAILS = ("-21", "–21", "/21", " to 21", " and 21", "; 21", ", 21") def test_title_tax_year_over_every_synthetic_title(): @@ -118,26 +128,71 @@ def test_title_tax_year_over_every_synthetic_title(): ): title = f"{prefix}Tax Years {first}{connector}{second}{suffix}" assert title_tax_year(title) == (first if first == second else None), title + # A span that ends in a year it cannot read ("2023-24", "2020 and 2021/22") + # names no single tax year, however many full years come before it. + for prefix, connector, tail, first, second in itertools.product( + PREFIXES, CONNECTORS, INCOMPLETE_TAILS, YEARS, YEARS + ): + assert title_tax_year(f"{prefix}Tax Year {first}{tail}") is None + title = f"{prefix}Tax Years {first}{connector}{second}{tail}" + assert title_tax_year(title) is None, title # --- the real package tree --------------------------------------------------- +def _artifact_declaration(path: Path) -> dict: + """A package's decoded ``artifact`` mapping. + + Decoding, not text search, decides which packages qualify, so a quoted key + or an escaped title cannot hide one. Only the part before the top-level + ``record_sets`` key is parsed, where ``artifact`` sits; parsing every + package in full takes about 50 seconds. The whole file is parsed when the + head has no ``artifact``. + """ + text = path.read_text(encoding="utf-8") + match = _TOP_LEVEL_RECORD_SETS.search(text) + head = safe_load(text[: match.start()]) if match else None + if not isinstance(head, dict) or "artifact" not in head: + head = safe_load(text) + artifact = head.get("artifact") if isinstance(head, dict) else None + return artifact if isinstance(artifact, dict) else {} + + def _single_tax_year_packages() -> dict[str, int]: """Pinned packages whose source table title names exactly one tax year.""" packages = {} for sub in discover_source_package_dirs(REPO_ROOT / "packages"): - path = REPO_ROOT / "packages" / sub / SOURCE_PACKAGE_FILENAME - text = path.read_text(encoding="utf-8") - if "artifact_year:" not in text or not re.search(r"(?i)tax\s+year", text): + artifact = _artifact_declaration( + REPO_ROOT / "packages" / sub / SOURCE_PACKAGE_FILENAME + ) + if artifact.get("artifact_year") is None: continue - artifact = yaml.safe_load(text).get("artifact") or {} year = title_tax_year(str(artifact.get("source_table") or "")) if year is not None: packages[str(sub)] = year return dict(sorted(packages.items())) +@pytest.mark.parametrize( + "header", + [ + "artifact:\n artifact_year: 2020\n source_table: Tax Year 2020\n", + '"artifact":\n "artifact_year": 2020\n source_table: "Tax\\u0020Year 2020"\n', + "package_id: x\nartifact: {artifact_year: 2020, source_table: 'Tax Year 2020'}\n", + ], +) +def test_artifact_declaration_reads_any_yaml_spelling(tmp_path, header): + for tail in ("record_sets:\n- record_set_id: x\n", '"record_sets": []\n', ""): + path = tmp_path / "source_package.yaml" + path.write_text(header + tail, encoding="utf-8") + artifact = _artifact_declaration(path) + assert artifact["artifact_year"] == 2020 + assert title_tax_year(artifact["source_table"]) == 2020 + path.write_text("record_sets: []\n" + header, encoding="utf-8") + assert _artifact_declaration(path)["artifact_year"] == 2020 + + SINGLE_TAX_YEAR_PACKAGES = _single_tax_year_packages() @@ -161,12 +216,15 @@ def test_facts_carry_the_tax_year_their_source_table_names(package_path): if fact["period"]["type"] != "tax_year": continue assert fact["period"]["value"] == table_year, where - vintage = _TAX_YEAR_VINTAGE.fullmatch(fact["source"]["vintage"]) - if vintage is not None: - assert int(vintage.group(1)) == table_year, where + for vintage in ( + fact["source"].get("vintage"), + fact["measure"].get("legal_vintage"), + ): + stamped = _TAX_YEAR_VINTAGE.findall(str(vintage or "")) + assert {int(year) for year in stamped} <= {table_year}, (where, vintage) for identifier in (fact["source_record_id"], fact["layout"]["record_set_id"]): stamped = {int(year) for year in _TAX_YEAR_ID_TOKEN.findall(identifier)} - assert stamped <= {table_year}, (where, identifier) + assert stamped == {table_year}, (where, identifier) # --- IRS SOI Table 4.B (Form W-2 items) -------------------------------------- @@ -227,6 +285,12 @@ def test_w2_facts_are_the_published_tax_year_2020_cells(): assert fact["source"]["vintage"] == "tax_year_2020" assert fact["source"]["source_table"] == W2_TITLE assert fact["source"]["source_file"] == "20in04w2all.xlsx" + assert fact["measure"].get("legal_vintage") in (None, "tax_year_2020") + # The package declares a legal vintage on the tips amount only. + tips_amount = facts[ + "irs_soi.ty2020.form_w2_social_security_tips.box_7_social_security_tips.amount" + ] + assert tips_amount["measure"]["legal_vintage"] == "tax_year_2020" @pytest.mark.parametrize("year", [2019, 2021, 2022, 2023, 2024, 2025]) From b75203341aa096afdd61d1e2d5fc03838cfc2d0a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 18:07:29 -0400 Subject: [PATCH 3/3] Apply the round-2 review nits to the title reader and the harness doc - Refuse a title only when a separator is followed by a one- or two-digit fragment that ends the word (an abbreviated year such as 2023-24). Any other number after a separator ("Tax Year 2020, 1040 Returns", "Tax Year 2020 - 1st Release") no longer drops the title. - Read "or", "vs.", "thru" and U+2212 as separators, so "Tax Year 2020 or 2021" names two years, not 2020. - Say in the doc that the package must build a fact and that every fact's source_table must name the year, and that the year tokens are matched in lowercase. Co-Authored-By: Claude Opus 5.5 --- docs/agent-source-package-harness.md | 16 +++++----- tests/test_chronicle_source_table_year.py | 37 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/docs/agent-source-package-harness.md b/docs/agent-source-package-harness.md index aba943f7..58d4f9cb 100644 --- a/docs/agent-source-package-harness.md +++ b/docs/agent-source-package-harness.md @@ -867,13 +867,15 @@ mismatch. That check compares one build with another, so it cannot see a literal label that is simply the wrong year. `tests/test_chronicle_source_table_year.py` covers that case for every pinned package whose `artifact.source_table` names -exactly one tax year ("…, Tax Year 2020"). Each tax-year fact built at the -artifact year must have that year as its period. Any `tax_year_` in its -source vintage or legal vintage must name that year. It must also be the only -`ty` token in the fact's record id and record set id. A title that names -a range or abbreviates a year ("tax year 2023-24") is not read as one tax year -and is not checked. State the table title as the publisher printed it, with -its year, so the test has something to check. +exactly one tax year ("…, Tax Year 2020"). The package must build at least +one fact at its artifact year, and every fact's `source_table` must name that +same year. Each tax-year fact must have that year as its period. Any +`tax_year_` in its source vintage or legal vintage must name that year, +and it must be the only `ty` token in the fact's record id and record +set id (both tokens are matched in lowercase). A title that names a range or +abbreviates a year ("tax year 2023-24") is not read as one tax year and is +not checked. State the table title as the publisher printed it, with its +year, so the test has something to check. Agents may add new package directories and YAML specs. They should not modify `chronicle.core`, `chronicle.database`, or `chronicle.suite` unless the package cannot be diff --git a/tests/test_chronicle_source_table_year.py b/tests/test_chronicle_source_table_year.py index 7ff05e2c..46167ac5 100644 --- a/tests/test_chronicle_source_table_year.py +++ b/tests/test_chronicle_source_table_year.py @@ -42,14 +42,19 @@ REPO_ROOT = Path(__file__).resolve().parents[1] _YEAR = r"(? int | None: ("Individual returns, Tax Years 2020; 2021", None), ("Individual returns, Tax Year 2020 to 21", None), ("Individual returns, Tax Years 2020; 2020", 2020), + ("Tax Year 2020, 1040 Returns", 2020), + ("Tax Year 2020 - 1st Release", 2020), + ("Tax Year 2020 (Filing Year 2021)", 2020), + ("Tax Year 2020: All Returns", 2020), + ("Individual returns, Tax Year 2020 or 2021", None), + ("Individual returns, Tax Years 2020 vs. 2021", None), + ("Individual returns, Tax Years 2020 thru 2021", None), + ("Individual returns, Tax Years 2020 − 2021", None), ("Tax Year 20201", None), ("Estimates of Federal Tax Expenditures for Fiscal Years 2024-2028", None), ("Employer-Sponsored Insurance: Calendar Years 1987-2024", None), @@ -111,7 +124,21 @@ def test_title_tax_year_reads_publisher_titles(title, expected): PREFIXES = ("", "Table 4.B. Items for Taxpayers with Form W-2, ", "2020 release: ") SUFFIXES = ("", ".", " (revised)", " [1]", ", all returns") -CONNECTORS = (" and ", "-", "–", " to ", " through ", ", ", " & ", "; ", "/") +CONNECTORS = ( + " and ", + "-", + "–", + "−", + " to ", + " through ", + " thru ", + " or ", + " vs. ", + ", ", + " & ", + "; ", + "/", +) YEARS = (1999, 2019, 2020, 2023, 2099) INCOMPLETE_TAILS = ("-21", "–21", "/21", " to 21", " and 21", "; 21", ", 21")