From 4ced6a1a4cf2c8a57c2189b96022f32294401ac9 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sat, 12 Sep 2026 12:35:48 -0400 Subject: [PATCH 01/16] Add child-level childcare attendance donor preparation --- docs/us-childcare-attendance.md | 130 ++++++++++ .../build/us_runtime/childcare_attendance.py | 201 +++++++++++++++ .../tests/test_us_childcare_attendance.py | 233 ++++++++++++++++++ 3 files changed, 564 insertions(+) create mode 100644 docs/us-childcare-attendance.md create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py create mode 100644 packages/microcosm-build/tests/test_us_childcare_attendance.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md new file mode 100644 index 000000000..9c2ebd42d --- /dev/null +++ b/docs/us-childcare-attendance.md @@ -0,0 +1,130 @@ +# Child-care attendance inputs: source-backed preparation + +Related: [Microcosm #915](https://github.com/PolicyEngine/microcosm/issues/915). + +## Status and boundary + +`microcosm.build.us_runtime.childcare_attendance.impute_us_childcare_attendance` +is an **opt-in preparation primitive** with synthetic-fixture tests. It is not +registered in the US production pipeline. This change alone does not populate +published datasets or resolve #915. It does not change PolicyEngine-US defaults, +the engine ABI, or the existing SPM-unit childcare expense stage. + +The three outputs belong on the **person records of children receiving care**: + +- `childcare_attending_days_per_month` (integer) +- `childcare_days_per_week` +- `childcare_hours_per_day` + +These are YEAR-defined engine inputs describing attendance frequency, not annual +totals. `childcare_hours_per_week` remains derived by the engine from days times +hours. Do not put the child's schedule on parents or replicate it to every +household member. Separate attendance from CCDF eligibility, enrollment, and +subsidy receipt. Zero household expenses do not establish nonattendance, and +positive expenses do not establish a full-time schedule. + +## Implemented donor contract + +The function takes a person table, a normalized child donor table, positional +typed `Weights`, a seed, and exact matching columns including whole-year age. +Donors require unique canonical string `donor_id` values, complete matching +fields, and all three attendance inputs. Source normalization must remove +survey-specific missing codes; unknown attendance must not become zero. +Nonparticipants must be present as measured three-zero records with their +survey weights. The current donor domain is ages 0–12, matching the proposed +NSECE source scope, **not** a universal CCDF age-eligibility rule. + +The function draws a complete schedule with probability proportional to donor +survey weight among compatible donors. Thus participation and intensity remain +joint rather than making three independent predictions. Observed cells, +including zero, constrain the donor match and are never overwritten. Incomplete +or contradictory records, duplicate donors, missing matching fields, and empty +positive-weight matching cells raise errors. There is no fallback to a full-time +schedule or silent broadening of a matching cell. A fitted conditional model or +explicit sparse-cell strategy can later provide support without changing the +preservation contract. + +Recipients use canonical string `person_source_id` identifiers. Clones must have +identical matching fields and compatible observations. A stable hash of seed +and source person selects one donor for all clones; donor sorting makes the +selection independent of input order. Complete observations on a clone can fill +the others without donor support. All clones of a source person must be processed +together. Distinct siblings are not forced to share a schedule; joint household +matching remains a requirement to evaluate before activation. + +Every output has a companion `_source` column identifying observations, +donor assignments, or propagation from another copy of the source person. +Missing values outside the age domain remain null, while observed older-child +or adult values are retained. This is an intermediate table: it must **not** be +sent to the engine as a complete dataset until the source coverage and export +contracts below are implemented. Provenance must be retained in build artifacts +and excluded from the engine input projection. + +## Proposed source and remaining work + +The [2024 NSECE release](https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation?archive=cfda&tenant=icpsr) +contains a public household file (DS5) and household calendar file (DS4). +They are candidates for a child-level arrangement donor source; this PR does +not claim to have mapped or validated their raw fields. Download access requires +accepting ICPSR terms, including redistribution restrictions. No NSECE records +are included in the repository or fixtures. + +Before enabling this routine in the US build: + +1. Review permitted use of source and derived artifacts. Pin the release, + download coordinates, file hashes, survey year, and codebook references in a + source manifest. Implement a reproducible source adapter. Verify child and + household links, the appropriate survey weights, imputation flags, missing + codes, participation, calendar episodes, and provider categories. +2. Define an attendance estimand suitable for the engine: for example a + representative care week with documented school-year/summer treatment. + Derive attended days from the union of care days and daily hours from + non-overlapping care episodes. Do not sum different providers' hours and then + price all those hours at one provider's rate. Decide how to represent multiple + providers in a model with scalar attendance inputs. +3. Document the monthly conversion and rounding. For a representative-week + convention, `round(days_per_week * 52 / 12)` is one possible approximation + (five days maps to 22); it is **not** a measured monthly calendar and is not + implemented or imposed on observed monthly values here. Verify the convention + against consuming formulas before adoption. +4. Select matching features available on both source and target: age, school + attendance, family composition, parental work/activity, income, and supported + geography. Public-use region must not be represented as observed state. + Measure support and sparse-cell failures; evaluate household donor matching + for sibling coherence and a weighted conditional model for generalization. + Do not fit on replicated support clones as independent respondents. +5. Address older children with disabilities or other state-specific exceptions + using appropriate source evidence. An under-13 survey cannot establish zero + care for all older children. Define an explicit missing-data/export policy; + never silently coerce unresolved child inputs to zero. +6. Register the source operation and late producer, persist person outputs, + update source specifications and generated manifests, coverage declarations, + producer inventories, and engine-input ABI declarations through the normal + generation tools. Test build ordering and actual export/reload behavior. +7. Validate weighted participation and days/hours distributions by age, income, + family work pattern, and provider; inspect sibling and clone consistency. + Compare state CCDF outcomes before and after on real data. Record exact engine + and dataset versions. Synthetic positive-benefit examples do not certify + population estimates. + +Provider and activity inputs need their own evidence. Earlier diagnostic +households required explicit MA/MD provider types and Nevada's activity input +after attendance was supplied; populating attendance alone is not a complete +CCDF data solution. Likewise, +[PolicyEngine-US #9405](https://github.com/PolicyEngine/policyengine-us/issues/9405) +concerns which state benefits enter the household aggregate and is independent +of this source preparation. + +## Local checks + +```bash +uv sync --all-packages --locked --extra us +uv run pytest packages/microcosm-build/tests/test_us_childcare_attendance.py +uv run ruff check . +uv run python tools/ci_test_groups.py --verify +``` + +The donor tests use only explicitly synthetic records. They check survey-weighted +participation, joint schedules, preservation of observed zeros, source-clone +coherence, stable ordering/chunking, age scope, and rejection of unsupported or +invalid input. Production data validation remains separate from PR CI. diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py new file mode 100644 index 000000000..91ba39f34 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py @@ -0,0 +1,201 @@ +"""Joint, weighted child-care attendance transfer from normalized child donors. + +This is an opt-in preparation primitive, not an enabled US build stage. Source +adapters must supply validated child records, survey weights and shared matching +fields. See docs/us-childcare-attendance.md for the source/activation gates. +""" + +from __future__ import annotations + +import hashlib +import json + +import numpy as np +import pandas as pd + +from microcosm.frame import Weights + +US_CHILDCARE_ATTENDANCE_COLUMNS = ( + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day", +) + + +def _validate_attendance(table: pd.DataFrame, *, complete: bool) -> None: + values = table[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].to_numpy( + dtype=float, na_value=np.nan + ) + if complete and np.isnan(values).any(): + raise ValueError("Childcare donor attendance must be complete.") + if np.isinf(values).any() or (values < 0).any(): + raise ValueError("Childcare attendance must be finite and nonnegative.") + if (values > np.array([31, 7, 24])).any(): + raise ValueError("Childcare attendance exceeds calendar bounds.") + monthly = values[:, 0] + if (monthly[np.isfinite(monthly)] % 1 != 0).any(): + raise ValueError("Childcare attending days per month must be integral.") + if ((values == 0).any(axis=1) & (values > 0).any(axis=1)).any(): + raise ValueError("Childcare attendance mixes nonattendance and positive care.") + + +def _ids(table: pd.DataFrame, column: str, *, unique: bool) -> pd.Series: + ids = table[column] + if ( + ids.isna().any() + or not ids.map( + lambda value: isinstance(value, str) and bool(value.strip()) + ).all() + ): + raise ValueError(f"{column} must contain nonempty canonical string IDs.") + if unique and ids.duplicated().any(): + raise ValueError(f"{column} must be unique; donors cannot contain clones.") + return ids + + +def impute_us_childcare_attendance( + person: pd.DataFrame, + donor: pd.DataFrame, + *, + donor_weights: Weights, + match_columns: tuple[str, ...], + seed: int, +) -> pd.DataFrame: + """Fill missing attendance for ages 0–12 by a joint weighted donor draw. + + ``donor_weights`` aligns positionally to the donor table. Exact matching must + include age; other fields (e.g. parental work and region) are supplied by a + source adapter. No implicit relaxation of an unsupported cell is allowed. + Nonparticipants belong in the donor pool with three observed zeros. Costs, + employment, CCDF eligibility and receipt are never used as participation + flags here. + + Draw once per canonical ``person_source_id``. Merge compatible observations + across support clones before matching, retaining every observed cell. The + selected donor supplies all remaining cells jointly, including nonattendance. + Donor row order, recipient row order and chunk boundaries do not affect the + draw (provided each source person's clones stay together). + + Return a copy with per-variable ``_source`` provenance. Missing + cells outside the source age domain remain null, not inferred zeros. Calling + this primitive on its own output preserves that provenance and is idempotent. + """ + if ( + not match_columns + or "age" not in match_columns + or len(set(match_columns)) != len(match_columns) + or set(match_columns) & set(US_CHILDCARE_ATTENDANCE_COLUMNS) + ): + raise ValueError("Unique matching columns must include age, not attendance.") + for table, required in ( + (person, ("person_source_id", *match_columns)), + (donor, ("donor_id", *match_columns, *US_CHILDCARE_ATTENDANCE_COLUMNS)), + ): + missing = sorted(set(required) - set(table.columns)) + if missing: + raise ValueError(f"Childcare attendance requires columns: {missing}.") + if not table.columns.is_unique: + raise ValueError("Childcare attendance requires unique column names.") + _ids(person, "person_source_id", unique=False) + _ids(donor, "donor_id", unique=True) + if not isinstance(donor_weights, Weights): + raise TypeError("Childcare donors require typed survey Weights.") + if len(donor_weights) != len(donor): + raise ValueError("Childcare donor weights must align with donor rows.") + + result = person.copy(deep=True).reset_index(drop=True) + pool = donor.copy(deep=True).reset_index(drop=True) + for table in (result, pool): + age = pd.to_numeric(table["age"], errors="raise").to_numpy( + dtype=float, na_value=np.nan + ) + if not np.isfinite(age).all() or (age < 0).any() or (age % 1 != 0).any(): + raise ValueError("Childcare attendance requires finite whole-year ages.") + table["age"] = age + if (pool["age"] > 12).any(): + raise ValueError("Childcare donors must be children aged 0–12.") + children = result["age"] <= 12 + if ( + pool[list(match_columns)].isna().any().any() + or result.loc[children, list(match_columns)].isna().any().any() + ): + raise ValueError("Childcare matching fields must be complete for children.") + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + if column not in result: + result[column] = np.nan + for table in (result, pool): + table[column] = pd.to_numeric(table[column], errors="raise").astype(float) + provenance = f"{column}_source" + if provenance not in result: + result[provenance] = pd.Series(pd.NA, index=result.index, dtype="string") + observed = result[column].notna() & result[provenance].isna() + result.loc[observed, provenance] = "observed" + _validate_attendance(result, complete=False) + _validate_attendance(pool, complete=True) + pool["_donor_weight"] = donor_weights.values + pool = pool.sort_values("donor_id").reset_index(drop=True) + groups = pool.groupby(list(match_columns), sort=False, dropna=False).indices + + for source_id, rows in result.groupby( + "person_source_id", sort=False + ).groups.items(): + replicas = result.loc[rows] + if replicas[list(match_columns)].drop_duplicates().shape[0] != 1: + raise ValueError(f"Childcare clone matching fields disagree: {source_id}.") + if replicas["age"].iloc[0] > 12: + continue + known: dict[str, float] = {} + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + observed = replicas[column].dropna().unique() + if len(observed) > 1: + raise ValueError(f"Childcare clone observations disagree: {source_id}.") + if len(observed): + known[column] = float(observed[0]) + _validate_attendance( + pd.DataFrame([known], columns=US_CHILDCARE_ATTENDANCE_COLUMNS), + complete=False, + ) + missing = replicas[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].isna() + if not missing.any().any(): + continue + if len(known) == len(US_CHILDCARE_ATTENDANCE_COLUMNS): + selected = known + source = f"source_person:{source_id}" + else: + key = tuple(replicas[column].iloc[0] for column in match_columns) + # pandas uses scalar group keys for one matching column. + group_key = key[0] if len(key) == 1 else key + candidates = pool.iloc[groups.get(group_key, [])] + candidates = candidates.loc[candidates["_donor_weight"] > 0] + for column, value in known.items(): + candidates = candidates.loc[candidates[column] == value] + if candidates.empty: + raise ValueError( + f"No compatible positive-weight childcare donor: {source_id}." + ) + # Scale before summing to avoid overflow from large survey weights. + weights = candidates["_donor_weight"].to_numpy(dtype=float) + weights = weights / weights.max() + cumulative = np.cumsum(weights / weights.sum()) + payload = json.dumps([int(seed), "childcare_attendance", source_id]) + digest = hashlib.sha256(payload.encode()).digest() + draw = (int.from_bytes(digest[:8], "big") >> 11) / 2**53 + position = min( + int(np.searchsorted(cumulative, draw, side="right")), + len(candidates) - 1, + ) + selected = candidates.iloc[position] + source = f"donor:{selected['donor_id']}" + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + empty_rows = replicas.index[replicas[column].isna()] + result.loc[empty_rows, column] = selected[column] + result.loc[empty_rows, f"{column}_source"] = source + + _validate_attendance(result, complete=False) + result[US_CHILDCARE_ATTENDANCE_COLUMNS[0]] = result[ + US_CHILDCARE_ATTENDANCE_COLUMNS[0] + ].astype("Int64") + # Preserve caller indices, ordering and all unrelated source columns. + result.index = person.index + result["age"] = person["age"] + return result diff --git a/packages/microcosm-build/tests/test_us_childcare_attendance.py b/packages/microcosm-build/tests/test_us_childcare_attendance.py new file mode 100644 index 000000000..9bc4a0eca --- /dev/null +++ b/packages/microcosm-build/tests/test_us_childcare_attendance.py @@ -0,0 +1,233 @@ +"""Behavioral contracts for the opt-in childcare attendance donor primitive. + +All donors below are synthetic test records, not survey estimates. +""" + +import numpy as np +import pandas as pd +import pytest +from pandas.testing import assert_frame_equal + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + impute_us_childcare_attendance, +) +from microcosm.frame import WeightKind, Weights + +MONTH, DAYS, HOURS = US_CHILDCARE_ATTENDANCE_COLUMNS + + +def _donors(): + return pd.DataFrame( + { + "donor_id": ["none", "part", "full"], + "age": [3, 3, 3], + MONTH: [0, 13, 22], + DAYS: [0.0, 3.0, 5.0], + HOURS: [0.0, 4.0, 8.0], + } + ) + + +def _people(count=1): + return pd.DataFrame( + {"person_source_id": [f"c:{i}" for i in range(count)], "age": [3] * count} + ) + + +def _impute(person, donor=None, weights=None, **kwargs): + donor = _donors() if donor is None else donor + return impute_us_childcare_attendance( + person, + donor, + donor_weights=Weights( + np.ones(len(donor)) if weights is None else np.asarray(weights), + WeightKind.DESIGN, + ), + match_columns=kwargs.pop("match_columns", ("age",)), + seed=kwargs.pop("seed", 42), + **kwargs, + ) + + +def test_joint_draw_respects_survey_weights_and_includes_nonparticipants(): + result = _impute(_people(1000), weights=[1, 0, 4]) + # Independent population behavior: 80% care, with sampling tolerance. + assert 0.75 < (result[DAYS] > 0).mean() < 0.85 + assert set(result[HOURS]) == {0, 8} + assert set(zip(result[MONTH], result[DAYS], result[HOURS], strict=True)) == { + (0, 0, 0), + (22, 5, 8), + } + assert str(result[MONTH].dtype) == "Int64" + + +def test_observed_zero_and_partial_positive_are_constraints_not_missing(): + person = _people(2).assign(**{DAYS: [0.0, 3.0]}) + original = person.copy(deep=True) + result = _impute(person) + assert result[MONTH].tolist() == [0, 13] + assert result[HOURS].tolist() == [0, 4] + assert result[f"{DAYS}_source"].tolist() == ["observed", "observed"] + assert_frame_equal(person, original) + assert_frame_equal(result, _impute(result)) + + +def test_expense_and_parental_work_are_not_participation_flags(): + people = _people(2).assign( + spm_unit_pre_subsidy_childcare_expenses=[0, 5000], + parent_hours_worked=[0, 40], + ) + cared_for = _impute(people, weights=[0, 0, 1]) + assert (cared_for[DAYS] == 5).all() + nonparticipants = _impute(people, weights=[1, 0, 0]) + assert (nonparticipants[DAYS] == 0).all() + + +def test_age_domain_and_person_level_values_leave_adults_and_older_children_null(): + people = _people(4).assign(age=[3, 35, 13, 16]) + people.loc[3, [MONTH, DAYS, HOURS]] = [13, 3, 4] + result = _impute(people, weights=[0, 1, 0]) + assert result.loc[0, DAYS] == 3 + assert result.loc[[1, 2], list(US_CHILDCARE_ATTENDANCE_COLUMNS)].isna().all().all() + assert result.loc[3, DAYS] == 3 # preserve observed older-child care + + +def test_clone_fanout_and_order_and_chunk_invariance(): + people = _people(8) + clones = people.iloc[:3].copy() + people = pd.concat([people, clones], ignore_index=True) + people["person_id"] = np.arange(len(people)) + donor = _donors() + expected = _impute(people, donor, weights=[1, 2, 3]) + shuffled = _impute( + people.sample(frac=1, random_state=12), + donor.iloc[::-1], + weights=[3, 2, 1], + ).sort_index() + assert_frame_equal(expected, shuffled) + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + assert (expected.groupby("person_source_id")[column].nunique() == 1).all() + # Keep all clones of a source person in the same chunk. + chunks = [ + people[people.person_source_id < "c:4"], + people[people.person_source_id >= "c:4"], + ] + actual = pd.concat( + [_impute(chunk, weights=[1, 2, 3]) for chunk in chunks] + ).sort_index() + assert_frame_equal(expected, actual) + + +def test_observations_on_one_clone_constrain_all_clones(): + people = pd.concat([_people(), _people()], ignore_index=True) + people.loc[1, DAYS] = 3 + result = _impute(people) + assert result[DAYS].tolist() == [3, 3] + assert result[HOURS].tolist() == [4, 4] + assert result.loc[1, f"{DAYS}_source"] == "observed" + + +def test_complete_clone_observations_need_no_donor_support(): + people = pd.concat([_people(), _people()], ignore_index=True) + people.loc[1, [MONTH, DAYS, HOURS]] = [17, 4, 6] + result = _impute(people) + assert result[MONTH].tolist() == [17, 17] + assert result.loc[0, f"{MONTH}_source"] == "source_person:c:0" + + +@pytest.mark.parametrize("column,value", [(DAYS, 5), ("age", 4)]) +def test_conflicting_clone_records_raise(column, value): + people = pd.concat([_people(), _people()], ignore_index=True).assign(**{DAYS: 3}) + people.loc[1, column] = value + with pytest.raises(ValueError, match="clone .* disagree"): + _impute(people) + + +def test_no_compatible_support_raises_without_overwriting_observed_values(): + person = _people().assign(**{DAYS: 4}) + with pytest.raises(ValueError, match="No compatible positive-weight"): + _impute(person) + assert person[DAYS].tolist() == [4] + + +def test_exact_covariate_matching_does_not_relax_to_another_group(): + people = _people().assign(parent_activity="working") + donor = _donors().assign(parent_activity=["not_working", "working", "not_working"]) + result = _impute(people, donor, match_columns=("age", "parent_activity")) + assert result[DAYS].tolist() == [3] + with pytest.raises(ValueError, match="No compatible positive-weight"): + _impute( + people, donor, weights=[1, 0, 1], match_columns=("age", "parent_activity") + ) + + +@pytest.mark.parametrize( + "column,value,message", + [ + (DAYS, -1, "nonnegative"), + (DAYS, 8, "calendar bounds"), + (HOURS, 25, "calendar bounds"), + (HOURS, np.inf, "finite"), + (MONTH, 13.5, "integral"), + (DAYS, np.nan, "complete"), + (HOURS, 0, "nonattendance"), + ("age", 13, "aged 0–12"), + ("age", np.nan, "whole-year ages"), + ], +) +def test_invalid_donor_records_raise(column, value, message): + donor = _donors() + donor[column] = donor[column].astype(float) + donor.loc[1, column] = value + with pytest.raises(ValueError, match=message): + _impute(_people(), donor) + + +def test_missing_matching_fields_and_duplicate_donor_ids_raise(): + with pytest.raises(ValueError, match="matching fields must be complete"): + _impute( + _people().assign(region=pd.NA), + _donors().assign(region="NE"), + match_columns=("age", "region"), + ) + donor = _donors() + donor.loc[1, "donor_id"] = "none" + with pytest.raises(ValueError, match="donors cannot contain clones"): + _impute(_people(), donor) + + +def test_weights_must_be_typed_and_aligned(): + with pytest.raises(ValueError, match="align"): + _impute(_people(), weights=[1, 1]) + with pytest.raises(TypeError, match="typed survey Weights"): + impute_us_childcare_attendance( + _people(), + _donors(), + donor_weights=np.ones(3), + match_columns=("age",), + seed=1, + ) + + +@pytest.mark.requires_us +def test_engine_accepts_child_inputs_and_derives_weekly_hours(): + from policyengine_us import Simulation + + result = _impute(_people(), weights=[0, 0, 1]) + year = 2026 + child = {"age": {year: 3}} + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + value = result.loc[0, column] + child[column] = {year: int(value) if column == MONTH else float(value)} + simulation = Simulation( + situation={ + "people": {"parent": {"age": {year: 30}}, "child": child}, + "households": {"household": {"members": ["parent", "child"]}}, + } + ) + assert simulation.calculate("childcare_hours_per_week", year).tolist() == [0, 40] + assert simulation.calculate(MONTH, year).tolist() == [0, 22] + # No mutation of the engine defaults by the dataset preparation function. + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + assert simulation.tax_benefit_system.variables[column].default_value == 0 From c45bf7ce063450fd63090b3ff103835e62e6be06 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sat, 12 Sep 2026 13:37:22 -0400 Subject: [PATCH 02/16] Classify childcare preparation in US runtime inventory --- docs/us-childcare-attendance.md | 7 +++++++ packages/microcosm-build/tests/test_us_spine_blindness.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index 9c2ebd42d..fcbfeaea1 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -120,6 +120,7 @@ of this source preparation. ```bash uv sync --all-packages --locked --extra us uv run pytest packages/microcosm-build/tests/test_us_childcare_attendance.py +uv run pytest packages/microcosm-build/tests/test_us_spine_blindness.py uv run ruff check . uv run python tools/ci_test_groups.py --verify ``` @@ -128,3 +129,9 @@ The donor tests use only explicitly synthetic records. They check survey-weighte participation, joint schedules, preservation of observed zeros, source-clone coherence, stable ordering/chunking, age scope, and rejection of unsupported or invalid input. Production data validation remains separate from PR CI. + +The runtime inventory in `test_us_spine_blindness.py` explicitly classifies this +preparation module outside the population-treatment registry. That classification +does not exempt it from the all-runtime source-spine access scan. Registering it +as a production stage must update its classification as well as the source and +coverage contracts above. diff --git a/packages/microcosm-build/tests/test_us_spine_blindness.py b/packages/microcosm-build/tests/test_us_spine_blindness.py index 1bd8f5385..3e24d7950 100644 --- a/packages/microcosm-build/tests/test_us_spine_blindness.py +++ b/packages/microcosm-build/tests/test_us_spine_blindness.py @@ -231,6 +231,9 @@ "block_ladder_sources.py", "capital_gain_distributions.py", "casualty_losses.py", + # Opt-in normalized-donor preparation; not a registered build stage. + # Still scanned for source-spine access by the all-runtime guard. + "childcare_attendance.py", "congressional_district_geography.py", "congressional_district_vintage.py", "congressional_district_vintage_crosswalk.py", From f065a3ae5e3a1dfe396b33cb5b8249ed9e819e59 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sat, 12 Sep 2026 14:49:49 -0400 Subject: [PATCH 03/16] Add verified NSECE attendance adapter and candidate diagnostics --- docs/us-childcare-attendance.md | 193 +++++--- experiments/us-childcare-attendance/README.md | 89 ++++ .../nsece-2024-v1-validation.json | 412 +++++++++++++++++ .../build/us_runtime/nsece_childcare.py | 414 ++++++++++++++++++ .../tests/test_us_nsece_childcare.py | 274 ++++++++++++ .../tests/test_us_spine_blindness.py | 2 + tools/prepare_us_childcare_attendance.py | 130 ++++++ 7 files changed, 1461 insertions(+), 53 deletions(-) create mode 100644 experiments/us-childcare-attendance/README.md create mode 100644 experiments/us-childcare-attendance/nsece-2024-v1-validation.json create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py create mode 100644 packages/microcosm-build/tests/test_us_nsece_childcare.py create mode 100644 tools/prepare_us_childcare_attendance.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index fcbfeaea1..ef77c8934 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -5,9 +5,11 @@ Related: [Microcosm #915](https://github.com/PolicyEngine/microcosm/issues/915). ## Status and boundary `microcosm.build.us_runtime.childcare_attendance.impute_us_childcare_attendance` -is an **opt-in preparation primitive** with synthetic-fixture tests. It is not -registered in the US production pipeline. This change alone does not populate -published datasets or resolve #915. It does not change PolicyEngine-US defaults, +is an **opt-in preparation primitive**. The verified NSECE source adapter and +`with_us_nsece_childcare_attendance` now support a local candidate `Frame` and +checkpoint flow, with aggregate diagnostics on the downloaded survey. Neither +operation is registered in the default US production pipeline. This change does +not populate published datasets or resolve #915. It does not change PolicyEngine-US defaults, the engine ABI, or the existing SPM-unit childcare expense stage. The three outputs belong on the **person records of children receiving care**: @@ -56,56 +58,140 @@ Every output has a companion `_source` column identifying observations donor assignments, or propagation from another copy of the source person. Missing values outside the age domain remain null, while observed older-child or adult values are retained. This is an intermediate table: it must **not** be -sent to the engine as a complete dataset until the source coverage and export -contracts below are implemented. Provenance must be retained in build artifacts +sent to the engine as a complete dataset until unresolved values are addressed +and the source coverage and production contracts below are satisfied. Provenance must be retained in build artifacts and excluded from the engine input projection. -## Proposed source and remaining work - -The [2024 NSECE release](https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation?archive=cfda&tenant=icpsr) -contains a public household file (DS5) and household calendar file (DS4). -They are candidates for a child-level arrangement donor source; this PR does -not claim to have mapped or validated their raw fields. Download access requires -accepting ICPSR terms, including redistribution restrictions. No NSECE records -are included in the repository or fixtures. - -Before enabling this routine in the US build: - -1. Review permitted use of source and derived artifacts. Pin the release, - download coordinates, file hashes, survey year, and codebook references in a - source manifest. Implement a reproducible source adapter. Verify child and - household links, the appropriate survey weights, imputation flags, missing - codes, participation, calendar episodes, and provider categories. -2. Define an attendance estimand suitable for the engine: for example a - representative care week with documented school-year/summer treatment. - Derive attended days from the union of care days and daily hours from - non-overlapping care episodes. Do not sum different providers' hours and then - price all those hours at one provider's rate. Decide how to represent multiple - providers in a model with scalar attendance inputs. -3. Document the monthly conversion and rounding. For a representative-week - convention, `round(days_per_week * 52 / 12)` is one possible approximation - (five days maps to 22); it is **not** a measured monthly calendar and is not - implemented or imposed on observed monthly values here. Verify the convention - against consuming formulas before adoption. -4. Select matching features available on both source and target: age, school - attendance, family composition, parental work/activity, income, and supported - geography. Public-use region must not be represented as observed state. - Measure support and sparse-cell failures; evaluate household donor matching - for sibling coherence and a weighted conditional model for generalization. - Do not fit on replicated support clones as independent respondents. -5. Address older children with disabilities or other state-specific exceptions - using appropriate source evidence. An under-13 survey cannot establish zero - care for all older children. Define an explicit missing-data/export policy; - never silently coerce unresolved child inputs to zero. -6. Register the source operation and late producer, persist person outputs, - update source specifications and generated manifests, coverage declarations, - producer inventories, and engine-input ABI declarations through the normal - generation tools. Test build ordering and actual export/reload behavior. -7. Validate weighted participation and days/hours distributions by age, income, - family work pattern, and provider; inspect sibling and clone consistency. - Compare state CCDF outcomes before and after on real data. Record exact engine - and dataset versions. Synthetic positive-benefit examples do not certify - population estimates. +## Verified source adapter + +The [2024 NSECE V1 release](https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation) +contains public household (DS5) and calendar (DS4) files. Both were downloaded +under the ICPSR agreement for this work. Each has 6,403 household records. The +loader verifies exact TSV SHA-256 hashes before parsing; hashes and byte counts +are recorded in the validation report. Raw records, donor tables, and source +archives remain local and are not included in the repository or CI fixtures. + +The mapping follows the Household Data Files User's Guide, printed pages HH-57, +HH-81, HH-279–280, HH-334, HH-554, and HH-565–568: + +| Source field | Adapter meaning | +| --- | --- | +| `HH4_METH_CASEID` | One-to-one household/calendar join; child suffixes agree across files | +| `HHC4_AGE_AT_USAGE_X` | Reference-week age in months; divide by 12 and floor; -9 means no child | +| `HHC4_METH_WEIGHT_X` | Child design weight, positive for present children | +| `HH4_MISSING_STATUS_CC_X` | 0 missing calendar, 1 partial, 2 complete | +| `HH4_CHCAL_R_X_Z` | 672 successive 15-minute blocks, starting Monday midnight | +| `HH4_TYPEOFCARE_AGG_X_Y` | Provider type for child X and provider Y | +| `HH4_REGION` | Four Census regions, not observed state | +| `HH4_PARWORK_STATUS` | Parents of any household child: -1 no parents, 0 none worked, 1 some worked, 2 all worked | +| `HH4_METH_QUEXVERSION` | Main, summer, or new-school-year questionnaire; retained for diagnostics | +| `HH4_ECON_INCOME_ANNUAL` | Annual household income for 2023; retained, not currently matched | + +ECE includes individual paid/unpaid care, centers, other organizations, and +irregular arrangements (types 1–5 and 7). K–8 schooling (type 6) is excluded. +The calendar already identifies one final provider per block. Attendance is the +union of ECE blocks: days count days with any ECE, and hours per day equal total +weekly ECE hours divided by those days. Multiple providers are counted for +subsequent diagnostics, but this does not solve provider-specific pricing. + +Monthly days use `floor(days_per_week * 52 / 12 + 0.5)`: five weekly days map to +22 monthly days. This is an explicit representative-week approximation, not an +observed month or an engine default. Summer and new-school-year questionnaires +lack the required calendar; weekly-hours summaries alone cannot recover days. + +**Missing calendars must not become observed zeros.** The guide warns that some +summary variables encode absent calendars as parental-only care. The adapter +requires a complete calendar and classifiable blocks. Unknown provider types, +missing blocks, and ambiguous gap-check codes leave all three inputs null with +an exclusion reason. A complete parental/self-care or school-only calendar is a +valid weighted zero donor. Unsupported codes are never guessed. + +## Candidate integration and reproduction + +Run locally after obtaining both TSVs under the archive's terms: + +```bash +uv sync --all-packages --locked --extra us +uv run python tools/prepare_us_childcare_attendance.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --report /local/nsece-validation.json +``` + +To apply to a local US `Frame` checkpoint, add +`--input-checkpoint /local/parent.h5 --output-checkpoint /local/candidate.h5`. +The output paths must be new. The operation preserves entity links, row order, +weights, strata, mass history, and inherited metadata. Source hashes, matching +columns, seed, and a candidate-only receipt accompany the checkpoint; the report +also records environment versions and code hashes. It neither publishes a +release nor updates production manifests. + +The target person table must already contain canonical `person_source_id` plus +harmonized `age`, `region`, and `parent_work_status`. These are the default exact +matching columns. Parent work must be derived from actual parent relationships, +not by classifying every adult as a parent. This CLI does not yet perform that +harmonization for production population sources. `--match-columns` allows +explicit experiments; age is mandatory. Existing known attendance, including +zero, is preserved. The caller must distinguish genuinely observed zeros from +previously materialized engine defaults before invoking the operation. + +Unknown attendance outside ages 0–12 remains null. Before an engine export, +`assert_childcare_attendance_exportable` requires all three columns to be finite +for every person; it does not certify representativeness. Retain provenance in +the checkpoint and project only canonical inputs to the engine. A synthetic +integration test exports a `Frame`, reloads it through `USSingleYearDataset`, and +verifies child weekly hours in a real `Microsimulation`. The CLI was also run +with the real source and a synthetic target checkpoint; this establishes data +flow, not population validity. + +## Actual source diagnostics + +See [the recorded experiment](../experiments/us-childcare-attendance/README.md) +and its aggregate JSON. Of 11,745 source children, 134 are outside ages 0–12. +There are 7,120 complete, unambiguous schedules, covering **60.26%** of weighted +under-13 children. Another 3,095 have missing calendars, 174 partial calendars, +and 1,222 ambiguous calendars. Original survey weights do not by themselves +make this selected subset nationally representative. + +A deterministic household split holds out 1,399 children and trains on 5,721; +no household occurs on both sides. Matching age, region, and parent work leaves +three held-out children unsupported, explicitly excluded from scoring. Among +1,396 supported children, weighted participation is **44.82% observed versus +45.99% imputed**; average weekly days are 1.82 versus 1.79 and weekly hours +13.65 versus 13.09. These are means across participants and nonparticipants. + +Age-and-region-only matching initially substantially understated attendance for +all-working-parent households and overstated it for some-working-parent +households. Adding parent work improves those comparisons, but the same split +was reused in development. It is a diagnostic, not an untouched final test set. +Subgroup differences, sampling uncertainty, missing-calendar selection, and +population transfer remain unresolved. The report explicitly records +`production_ready: false`. + +## Remaining production acceptance work + +1. Address calendar selection and seasonality using source design/response + evidence; quantify differences between included and excluded children and + validate a response adjustment or complementary source. Retain unknowns as + unknowns. Do not simply reuse the selected sample's weights as national totals. +2. Implement and test source-specific target harmonization for parent links, + work/activity, region, school status, income year, and stable source IDs. + Measure target matching support. Specify and validate a sparse-cell model + rather than silently broadening failed exact matches. +3. Evaluate joint household assignments for sibling coherence, mixed-provider + schedules against the engine's scalar provider inputs, and the monthly + conversion against consuming formulas. Resolve older children, including + disability-related care, and other out-of-domain records before export. +4. Register the operation and late producer through normal generation tools; + update source specs, generated manifests, coverage and producer inventories, + and engine input contracts. Verify build ordering on a real parent artifact. + The opt-in candidate path here is not that default-build activation. +5. Run a full candidate population with pinned parent/engine/code identities. + Predeclare validation criteria, use fresh evaluation data or cross-validation, + quantify uncertainty, and inspect participation and days/hours distributions + by age, income, work pattern, geography, and provider. Compare state CCDF + outcomes before/after; synthetic positive-benefit examples cannot certify + those population estimates. Provider and activity inputs need their own evidence. Earlier diagnostic households required explicit MA/MD provider types and Nevada's activity input @@ -120,6 +206,7 @@ of this source preparation. ```bash uv sync --all-packages --locked --extra us uv run pytest packages/microcosm-build/tests/test_us_childcare_attendance.py +uv run pytest packages/microcosm-build/tests/test_us_nsece_childcare.py uv run pytest packages/microcosm-build/tests/test_us_spine_blindness.py uv run ruff check . uv run python tools/ci_test_groups.py --verify @@ -130,8 +217,8 @@ participation, joint schedules, preservation of observed zeros, source-clone coherence, stable ordering/chunking, age scope, and rejection of unsupported or invalid input. Production data validation remains separate from PR CI. -The runtime inventory in `test_us_spine_blindness.py` explicitly classifies this -preparation module outside the population-treatment registry. That classification +The runtime inventory in `test_us_spine_blindness.py` explicitly classifies both +preparation modules outside the population-treatment registry. That classification does not exempt it from the all-runtime source-spine access scan. Registering it as a production stage must update its classification as well as the source and coverage contracts above. diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md new file mode 100644 index 000000000..f798e123e --- /dev/null +++ b/experiments/us-childcare-attendance/README.md @@ -0,0 +1,89 @@ +# NSECE attendance candidate: 2026-09-12 diagnostic + +Related: [#915](https://github.com/PolicyEngine/microcosm/issues/915) and +[draft PR #916](https://github.com/PolicyEngine/microcosm/pull/916). + +**Verdict: candidate only; not ready for production activation.** The real source +adapter and local checkpoint path work. This experiment does not establish +national representativeness, target population validity, or state CCDF effects. + +The [aggregate report](nsece-2024-v1-validation.json) was generated from the +2024 NSECE public household DS5 and calendar DS4 TSVs, ICPSR 39466 V1. It records +source hashes/size, seed, exact code hashes, and Python/library versions. It +contains no individual records or identifiers. Source archives, normalized donor +records, and local candidate checkpoints are not redistributed in this PR. +See the [mapping and reproduction guide](../../docs/us-childcare-attendance.md). + +## Source coverage + +| Status | Children | +| --- | ---: | +| Complete and unambiguous | 7,120 | +| Missing calendar | 3,095 | +| Partial calendar | 174 | +| Ambiguous calendar/provider | 1,222 | +| Age outside 0–12 | 134 | +| Total | 11,745 | + +Complete schedules cover **60.26%** of the original weighted under-13 population. +This is source coverage, not an attendance participation rate. The original +child weights are used for conditional donor draws and descriptive comparisons; +using them after these exclusions does not validate national totals. Calendar +availability differs by questionnaire version, including the summer and +new-school-year instruments. Missing and ambiguous attendance stays unknown. + +## Household-separated diagnostic + +A stable hash with seed 915 holds out approximately 20% of households, keeping +siblings on the same side: 5,721 training children and 1,399 held-out children, +with zero household overlap. The final candidate matches exact child age, +Census region, and household parent-work status. Three held-out children lack +training support (weight 44,357.42) and are reported separately; 1,396 are scored. +No unsupported record is assigned a default zero. + +| Weighted mean, supported children | Observed | Imputed | +| --- | ---: | ---: | +| Any ECE attendance | 44.82% | 45.99% | +| Days per week | 1.823 | 1.793 | +| Hours per week | 13.652 | 13.095 | + +Means include participants and nonparticipants. Matching draws all three +attendance inputs jointly; it does not independently predict their means. + +| Parent work group | Observed participation | Imputed participation | +| --- | ---: | ---: | +| All parents worked | 60.79% | 59.14% | +| Some parents worked | 25.44% | 30.49% | +| No parents worked | 28.59% | 33.79% | +| No parents present, supported subset | 28.83% | 20.26% | + +An earlier age-and-region-only diagnostic on the same household split imputed +41.51% participation for all-working-parent households and 47.03% for +some-working-parent households, compared with observed 60.79% and 25.44%. +That motivated adding parent work. Consequently this split has been used during +model development and must not be treated as an untouched final acceptance set. +Age, region, and work-group detail is retained in the JSON; for example the +age-zero weekly-hours mean remains 9.85 imputed versus 13.48 observed. No +uncertainty intervals or acceptance thresholds have yet been established. + +## Integration evidence and limits + +The report's `candidate_frame_written: true` refers to a **two-person synthetic +target checkpoint with the real survey donor source**, not a production build. +The local CLI run preserved parent receipt metadata, entity links and household +weight, wrote a separate candidate checkpoint, and reloaded its attendance +columns without missing values for this fixture. The fixture's adult had +explicit observed zeros; the adapter did not infer adult zero attendance. + +A separate synthetic-source CI test exports through `PolicyEngineUSEngine`, +reloads through `USSingleYearDataset`, and calculates weekly hours through a +real `Microsimulation`. Locally this ran with PolicyEngine-US 1.819.0 and period +2026. It verifies the person input/export contract only. It does not validate +benefit changes or production data. + +The next acceptance work is source-selection/seasonality analysis, tested +harmonization of real target parent/work/region fields, sparse-cell handling, +sibling and provider conventions, older-child coverage, registered build and +input contracts, and a full population comparison with pinned parent and engine +artifacts. No release, production manifest change, or national CCDF result is +claimed here. diff --git a/experiments/us-childcare-attendance/nsece-2024-v1-validation.json b/experiments/us-childcare-attendance/nsece-2024-v1-validation.json new file mode 100644 index 000000000..8a8251642 --- /dev/null +++ b/experiments/us-childcare-attendance/nsece-2024-v1-validation.json @@ -0,0 +1,412 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "source_child_count": 11745, + "source_attrition": [ + { + "status": "age_out_of_scope", + "n": 134, + "child_weight": 633390.6468148227 + }, + { + "status": "ambiguous_calendar", + "n": 1222, + "child_weight": 5389746.35438953 + }, + { + "status": "complete", + "n": 7120, + "child_weight": 30778083.363609407 + }, + { + "status": "missing_calendar", + "n": 3095, + "child_weight": 14061828.648228496 + }, + { + "status": "partial_calendar", + "n": 174, + "child_weight": 847769.9869632281 + } + ], + "under13_complete_weight_share": 0.6025769964530093, + "seed": 915, + "evaluation_design": "fixed household diagnostic split; not final population certification", + "match_columns": [ + "age", + "region", + "parent_work_status" + ], + "training_children": 5721, + "heldout_children": 1399, + "unsupported_heldout_children": 3, + "unsupported_heldout_weight": 44357.422786916, + "household_overlap": 0, + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1396, + "observed": { + "participation": 0.44824430561905076, + "days_per_week": 1.8230211691271492, + "hours_per_week": 13.652357584014506 + }, + "predicted": { + "participation": 0.4599107708663381, + "days_per_week": 1.792538633803043, + "hours_per_week": 13.094746582956082 + } + }, + { + "grouping": "age", + "group": "0", + "n": 85, + "observed": { + "participation": 0.4250548518820099, + "days_per_week": 1.6272888682251536, + "hours_per_week": 13.475219723863841 + }, + "predicted": { + "participation": 0.4001916699342544, + "days_per_week": 1.1485583331917613, + "hours_per_week": 9.848867658204549 + } + }, + { + "grouping": "age", + "group": "1", + "n": 110, + "observed": { + "participation": 0.5884762506267653, + "days_per_week": 2.113900852594937, + "hours_per_week": 15.642081332218696 + }, + "predicted": { + "participation": 0.546281902566076, + "days_per_week": 2.2283167307047886, + "hours_per_week": 18.453955475585847 + } + }, + { + "grouping": "age", + "group": "2", + "n": 109, + "observed": { + "participation": 0.6638776717499305, + "days_per_week": 3.1543151104205833, + "hours_per_week": 31.753365600922873 + }, + "predicted": { + "participation": 0.6675303263843991, + "days_per_week": 2.153101731321593, + "hours_per_week": 19.06318009755674 + } + }, + { + "grouping": "age", + "group": "3", + "n": 103, + "observed": { + "participation": 0.5305706046713187, + "days_per_week": 2.438879416678265, + "hours_per_week": 18.1763127663353 + }, + "predicted": { + "participation": 0.7244282687897443, + "days_per_week": 3.5317307356291354, + "hours_per_week": 31.010642124434668 + } + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed": { + "participation": 0.6728458027114763, + "days_per_week": 3.0618827336322867, + "hours_per_week": 21.645846223858417 + }, + "predicted": { + "participation": 0.7679834812046982, + "days_per_week": 3.0884408007199, + "hours_per_week": 22.77807200190468 + } + }, + { + "grouping": "age", + "group": "5", + "n": 108, + "observed": { + "participation": 0.44276847764953914, + "days_per_week": 2.098241639215739, + "hours_per_week": 15.395426983272767 + }, + "predicted": { + "participation": 0.5098892206159185, + "days_per_week": 2.034518834281255, + "hours_per_week": 12.616686226131161 + } + }, + { + "grouping": "age", + "group": "6", + "n": 114, + "observed": { + "participation": 0.3316947363796176, + "days_per_week": 1.2944962438275214, + "hours_per_week": 9.335974428220535 + }, + "predicted": { + "participation": 0.2502829109705292, + "days_per_week": 0.8489748828146265, + "hours_per_week": 4.095594700920569 + } + }, + { + "grouping": "age", + "group": "7", + "n": 117, + "observed": { + "participation": 0.24608381033211157, + "days_per_week": 0.754974779448484, + "hours_per_week": 4.361409561749528 + }, + "predicted": { + "participation": 0.2816097434308538, + "days_per_week": 1.2011659245211868, + "hours_per_week": 8.378054810826729 + } + }, + { + "grouping": "age", + "group": "8", + "n": 131, + "observed": { + "participation": 0.32031380136011167, + "days_per_week": 1.1016613467219087, + "hours_per_week": 5.702965816381827 + }, + "predicted": { + "participation": 0.3616416815522732, + "days_per_week": 1.3941828221834964, + "hours_per_week": 6.393127232702683 + } + }, + { + "grouping": "age", + "group": "9", + "n": 96, + "observed": { + "participation": 0.34656269944559637, + "days_per_week": 1.3599782610569988, + "hours_per_week": 6.897690557738436 + }, + "predicted": { + "participation": 0.43275434548357045, + "days_per_week": 1.7893869343598068, + "hours_per_week": 12.468455293838895 + } + }, + { + "grouping": "age", + "group": "10", + "n": 101, + "observed": { + "participation": 0.42051506588928955, + "days_per_week": 1.4586593950757574, + "hours_per_week": 9.973639524210338 + }, + "predicted": { + "participation": 0.24474633593038378, + "days_per_week": 0.9756762848157232, + "hours_per_week": 6.008858030670072 + } + }, + { + "grouping": "age", + "group": "11", + "n": 112, + "observed": { + "participation": 0.3912389480210642, + "days_per_week": 1.7037510807546974, + "hours_per_week": 11.42197768791282 + }, + "predicted": { + "participation": 0.27792753401822695, + "days_per_week": 0.8090302361512782, + "hours_per_week": 7.743373773952352 + } + }, + { + "grouping": "age", + "group": "12", + "n": 95, + "observed": { + "participation": 0.42381513275076016, + "days_per_week": 1.3666357724773346, + "hours_per_week": 10.404992746238733 + }, + "predicted": { + "participation": 0.5490190603072337, + "days_per_week": 2.3346754573311457, + "hours_per_week": 13.09355527945929 + } + }, + { + "grouping": "region", + "group": "1", + "n": 176, + "observed": { + "participation": 0.512049571332397, + "days_per_week": 2.3364280889303273, + "hours_per_week": 14.798702419320156 + }, + "predicted": { + "participation": 0.6213815652348272, + "days_per_week": 2.367934702106529, + "hours_per_week": 16.14377173776723 + } + }, + { + "grouping": "region", + "group": "2", + "n": 326, + "observed": { + "participation": 0.4156690296156914, + "days_per_week": 1.6707757670228114, + "hours_per_week": 13.50512496474333 + }, + "predicted": { + "participation": 0.4156827588489637, + "days_per_week": 1.4698531060718576, + "hours_per_week": 11.050552277269526 + } + }, + { + "grouping": "region", + "group": "3", + "n": 460, + "observed": { + "participation": 0.39711564348080924, + "days_per_week": 1.7104278494216743, + "hours_per_week": 14.327410707539498 + }, + "predicted": { + "participation": 0.40609226757761724, + "days_per_week": 1.6125118356079156, + "hours_per_week": 12.357699085016328 + } + }, + { + "grouping": "region", + "group": "4", + "n": 434, + "observed": { + "participation": 0.4998856798556513, + "days_per_week": 1.7358423509265488, + "hours_per_week": 12.03035641734164 + }, + "predicted": { + "participation": 0.4542107213405013, + "days_per_week": 1.9027766791574927, + "hours_per_week": 13.681380128977889 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 21, + "observed": { + "participation": 0.2883481772975941, + "days_per_week": 0.9379764610750094, + "hours_per_week": 3.544637613886705 + }, + "predicted": { + "participation": 0.20256052180939443, + "days_per_week": 0.9965262439605058, + "hours_per_week": 4.923861455170338 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 153, + "observed": { + "participation": 0.28587206721249403, + "days_per_week": 1.1510297379070302, + "hours_per_week": 9.85637208916276 + }, + "predicted": { + "participation": 0.33794446267152484, + "days_per_week": 1.381154311963177, + "hours_per_week": 8.994549754516147 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 556, + "observed": { + "participation": 0.2544421472652274, + "days_per_week": 0.8655226862627626, + "hours_per_week": 5.479055121121177 + }, + "predicted": { + "participation": 0.3048755294132399, + "days_per_week": 0.9735429342912938, + "hours_per_week": 6.619958653424124 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 666, + "observed": { + "participation": 0.6079397288949188, + "days_per_week": 2.598506997335398, + "hours_per_week": 20.1129358627005 + }, + "predicted": { + "participation": 0.591426968065257, + "days_per_week": 2.435925494751169, + "hours_per_week": 18.361765426147866 + } + } + ], + "production_ready": false, + "limitations": [ + "Only complete, unambiguous calendars enter this holdout; source selection remains unvalidated.", + "The diagnostic matches age and caller-specified covariates; it is not a nationally validated fitted model.", + "Sibling assignments, mixed providers, older-child care and scalar monthly conversion need validation.", + "No full Microcosm population build or state CCDF distribution is certified by this source-only report." + ], + "candidate_frame_written": true, + "environment": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "microcosm-build": "0.1.0", + "microcosm-frame": "0.1.0", + "python": "3.13.5" + }, + "code_sha256": { + "prepare_us_childcare_attendance.py": "5b83f0e3fb6b2b1e4c4264ffc594a1daefa1de3cd1d05f6bbadeb381d3b7bc88", + "childcare_attendance.py": "f7fede91b10ed5384a7197c1413958c5e526c47300b20e9248b08f12f46168b8", + "nsece_childcare.py": "39100db1e7ede461c37c0e6259a274233074d7838ec61616ed5d62302eb377ed" + } +} diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py new file mode 100644 index 000000000..caaec4d22 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py @@ -0,0 +1,414 @@ +"""Read the pinned 2024 NSECE V1 household and child-calendar public files. + +Source: ICPSR 39466 V1, Household Data Files User's Guide, HH-57, HH-81, +HH-279--280, HH-334, HH-554 and HH-565--568. This adapter measures ECE +attendance (including unpaid care), excluding K-8 schooling. It does not +determine licensed-provider status or CCDF eligibility/receipt. +""" + +from __future__ import annotations + +import hashlib +from dataclasses import dataclass +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + impute_us_childcare_attendance, +) +from microcosm.frame import US_SCHEMA, Frame, WeightKind, Weights + +NSECE_2024_HOUSEHOLD_SHA256 = ( + "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2" +) +NSECE_2024_CALENDAR_SHA256 = ( + "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e" +) +NSECE_CHILD_INDICES = tuple(range(1, 10)) +NSECE_PROVIDER_INDICES = tuple(range(1, 16)) +NSECE_CALENDAR_BLOCKS = 672 +NSECE_CHILDCARE_MATCH_COLUMNS = ("age", "region", "parent_work_status") +# HH-280: individual regular paid/unpaid, center, other organizational, irregular. +NSECE_ECE_TYPES = frozenset({1, 2, 3, 4, 5, 7}) +# HH-566--568: parental care, self-care, or school only. Mixed/unclear gap-check +# codes are deliberately unresolved, even if part of the interval involved ECE. +NSECE_NON_ECE_CALENDAR_CODES = frozenset({0, 50, 51, 52, 53, 56, 57, 60, 65, 68, 70}) + + +@dataclass(frozen=True) +class NSECEChildcareSource: + """All source children, including explicit reasons for unresolved schedules.""" + + children: pd.DataFrame + weights: Weights + source_receipt: dict[str, object] + + def donors(self) -> tuple[pd.DataFrame, Weights]: + usable = self.children["attendance_status"].eq("complete").to_numpy() + return ( + self.children.loc[usable].reset_index(drop=True).copy(), + Weights(self.weights.values[usable], self.weights.kind), + ) + + +def nsece_childcare_household_columns() -> tuple[str, ...]: + return ( + "HH4_METH_CASEID", + "HH4_METH_QUEXVERSION", + "HH4_REGION", + "HH4_PARWORK_STATUS", + "HH4_ECON_INCOME_ANNUAL", + *( + f"{prefix}_{child}" + for child in NSECE_CHILD_INDICES + for prefix in ( + "HHC4_AGE_AT_USAGE", + "HHC4_METH_WEIGHT", + "HH4_MISSING_STATUS_CC", + ) + ), + *( + f"HH4_TYPEOFCARE_AGG_{child}_{provider}" + for child in NSECE_CHILD_INDICES + for provider in NSECE_PROVIDER_INDICES + ), + ) + + +def nsece_childcare_calendar_columns() -> tuple[str, ...]: + return ( + "HH4_METH_CASEID", + *( + f"HH4_CHCAL_R_{child}_{block}" + for child in NSECE_CHILD_INDICES + for block in range(1, NSECE_CALENDAR_BLOCKS + 1) + ), + ) + + +def derive_nsece_childcare( + household: pd.DataFrame, calendar: pd.DataFrame +) -> NSECEChildcareSource: + """Normalize source children; never treat missing calendars as no care. + + A day is attended if any 15-minute ECE block occurs on that day. Average + daily hours are total ECE block-hours divided by attended days. Monthly + days use the explicit representative-week approximation floor(d * 52/12 + + 0.5). This is an approximation, not observed monthly attendance. Blocks + already encode a single final provider, so overlapping care is not added. + """ + for table, columns in ( + (household, nsece_childcare_household_columns()), + (calendar, nsece_childcare_calendar_columns()), + ): + missing = sorted(set(columns) - set(table.columns)) + if missing or not table.columns.is_unique: + raise ValueError(f"NSECE source columns invalid; missing={missing[:10]}.") + ids = table["HH4_METH_CASEID"] + if ids.isna().any() or ids.duplicated().any(): + raise ValueError("NSECE household IDs must be complete and unique.") + if set(household.HH4_METH_CASEID) != set(calendar.HH4_METH_CASEID): + raise ValueError("NSECE household/calendar ID sets must match exactly.") + hh = household.set_index("HH4_METH_CASEID").sort_index() + cal = calendar.set_index("HH4_METH_CASEID").reindex(hh.index) + parts = [] + for child in NSECE_CHILD_INDICES: + age_months = pd.to_numeric(hh[f"HHC4_AGE_AT_USAGE_{child}"], errors="raise") + if ( + not np.isfinite(age_months).all() + or (age_months != np.floor(age_months)).any() + or (~((age_months >= 0) | (age_months == -9))).any() + ): + raise ValueError("NSECE child age must be measured or the no-child code.") + present = age_months >= 0 + if not present.any(): + continue + rows = hh.loc[present] + values = cal.loc[ + present, + [f"HH4_CHCAL_R_{child}_{b}" for b in range(1, NSECE_CALENDAR_BLOCKS + 1)], + ].to_numpy(dtype=float) + types = rows[ + [f"HH4_TYPEOFCARE_AGG_{child}_{p}" for p in NSECE_PROVIDER_INDICES] + ].to_numpy(dtype=float) + status = rows[f"HH4_MISSING_STATUS_CC_{child}"].to_numpy(dtype=float) + if not np.isin(status, [0, 1, 2]).all(): + raise ValueError("Unknown NSECE calendar completeness code.") + ece = np.zeros(values.shape, dtype=bool) + known = np.isin(values, tuple(NSECE_NON_ECE_CALENDAR_CODES)) + provider_count = np.zeros(len(rows), dtype=int) + for p in NSECE_PROVIDER_INDICES: + used = values == p + ece_type = np.isin(types[:, p - 1], tuple(NSECE_ECE_TYPES)) + ece |= used & ece_type[:, None] + known |= used & (ece_type | (types[:, p - 1] == 6))[:, None] + provider_count += used.any(axis=1) & ece_type + age = np.floor(age_months.loc[present].to_numpy() / 12) + reason = np.select( + [age > 12, status == 0, status == 1, ~known.all(axis=1)], + [ + "age_out_of_scope", + "missing_calendar", + "partial_calendar", + "ambiguous_calendar", + ], + default="complete", + ) + complete = reason == "complete" + days = ece.reshape(-1, 7, 96).any(axis=2).sum(axis=1).astype(float) + weekly_hours = ece.sum(axis=1) / 4 + hours = np.divide(weekly_hours, days, out=np.zeros(len(rows)), where=days > 0) + monthly = np.floor(days * 52 / 12 + 0.5) + weight = pd.to_numeric(rows[f"HHC4_METH_WEIGHT_{child}"], errors="raise") + if not np.isfinite(weight).all() or (weight <= 0).any(): + raise ValueError("Existing NSECE children require positive child weights.") + part = pd.DataFrame( + { + "donor_id": [f"nsece2024:{case}:{child}" for case in rows.index], + "source_household_id": rows.index.astype(str), + "age": age.astype(int), + "region": rows.HH4_REGION.to_numpy(), + "parent_work_status": rows.HH4_PARWORK_STATUS.to_numpy(), + "household_income": rows.HH4_ECON_INCOME_ANNUAL.to_numpy(), + "questionnaire_version": rows.HH4_METH_QUEXVERSION.to_numpy(), + "attendance_status": reason, + "child_weight": weight.to_numpy(), + "ece_provider_count": np.where(complete, provider_count, np.nan), + "ece_hours_per_week": np.where(complete, weekly_hours, np.nan), + } + ) + for column, data in zip( + US_CHILDCARE_ATTENDANCE_COLUMNS, (monthly, days, hours), strict=True + ): + part[column] = np.where(complete, data, np.nan) + parts.append(part) + if not parts: + raise ValueError("NSECE source contains no children.") + children = ( + pd.concat(parts, ignore_index=True) + .sort_values("donor_id") + .reset_index(drop=True) + ) + return NSECEChildcareSource( + children, + Weights(children.child_weight.to_numpy(), WeightKind.DESIGN), + { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": False, + }, + ) + + +def load_nsece_childcare( + household_path: str | Path, calendar_path: str | Path +) -> NSECEChildcareSource: + """Load locally downloaded, hash-verified V1 TSV files; no automatic download.""" + tables = [] + receipts = [] + for path, expected_hash, columns in ( + ( + household_path, + NSECE_2024_HOUSEHOLD_SHA256, + nsece_childcare_household_columns(), + ), + (calendar_path, NSECE_2024_CALENDAR_SHA256, nsece_childcare_calendar_columns()), + ): + path = Path(path) + with path.open("rb") as stream: + actual = hashlib.file_digest(stream, "sha256").hexdigest() + if actual != expected_hash: + raise ValueError(f"NSECE source hash mismatch: {path.name}.") + tables.append( + pd.read_csv(path, sep="\t", usecols=list(columns), na_values=[" "]) + ) + receipts.append({"sha256": actual, "size_bytes": path.stat().st_size}) + result = derive_nsece_childcare(*tables) + result.source_receipt["artifacts"] = receipts + return result + + +def with_us_nsece_childcare_attendance( + frame: Frame, + source: NSECEChildcareSource, + *, + seed: int, + match_columns: tuple[str, ...], +) -> Frame: + """Apply the source to a candidate Frame, preserving links, weights and receipts. + + Matching fields must already be harmonized on person rows. This function + does not label all adults as parents or guess parental work from household + earnings. Missing values outside ages 0--12 remain unresolved. The caller + must pass ``assert_childcare_attendance_exportable`` before engine export. + This is not registered in the default production build. + """ + if frame.schema != US_SCHEMA: + raise ValueError("NSECE childcare attendance requires the US schema.") + donor, weights = source.donors() + people = impute_us_childcare_attendance( + frame.table("person"), + donor, + donor_weights=weights, + seed=seed, + match_columns=match_columns, + ) + # Float storage supports unresolved nulls in ordinary Frame checkpoints; + # the monthly variable has already been validated to be integral when known. + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + people[column] = people[column].astype(float) + tables = {entity: frame.table(entity).copy() for entity in frame.entities} + tables["person"] = people + return Frame( + tables, + frame.schema, + {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={ + **frame.metadata, + "nsece_childcare_attendance": { + **source.source_receipt, + "seed": int(seed), + "match_columns": match_columns, + "candidate_only": True, + }, + }, + ) + + +def assert_childcare_attendance_exportable(frame: Frame) -> None: + """Reject unresolved inputs before an engine can coerce them to default zero. + + This is a completeness check, not statistical or publication approval. + """ + people = frame.table("person") + missing = [c for c in US_CHILDCARE_ATTENDANCE_COLUMNS if c not in people] + if missing: + raise ValueError(f"Childcare export is missing inputs: {missing}.") + values = people[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].to_numpy( + dtype=float, na_value=np.nan + ) + if not np.isfinite(values).all(): + raise ValueError( + "Childcare export has unresolved attendance; do not fill with zero." + ) + + +def nsece_childcare_validation_report( + source: NSECEChildcareSource, + *, + seed: int = 915, + match_columns: tuple[str, ...] = NSECE_CHILDCARE_MATCH_COLUMNS, +) -> dict[str, object]: + """Source attrition and a household-separated 20% holdout; never certification. + + Holdout households are excluded from the donor pool before any prediction. + Source selection and this diagnostic's limited covariates remain explicit. + Repeated use of this fixed holdout does not create fresh independent evidence. + """ + children = source.children + donors, _ = source.donors() + in_domain = children.age.between(0, 12) + domain_mass = float(children.loc[in_domain, "child_weight"].sum()) + complete_mass = float(donors.child_weight.sum()) + split = donors.source_household_id.map( + lambda value: ( + int.from_bytes( + hashlib.sha256(f"{seed}:holdout:{value}".encode()).digest()[:8], "big" + ) + % 5 + == 0 + ) + ) + train = donors.loc[~split].reset_index(drop=True) + heldout = donors.loc[split].reset_index(drop=True) + if train.empty or heldout.empty: + raise ValueError("NSECE validation needs training and held-out households.") + train_cells = set(train[list(match_columns)].itertuples(index=False, name=None)) + supported = np.array( + [ + key in train_cells + for key in heldout[list(match_columns)].itertuples(index=False, name=None) + ] + ) + observed = heldout.loc[supported].reset_index(drop=True) + if observed.empty: + raise ValueError("NSECE holdout has no supported matching cells.") + recipients = observed.drop(columns=list(US_CHILDCARE_ATTENDANCE_COLUMNS)).assign( + person_source_id=observed.donor_id + ) + predicted = impute_us_childcare_attendance( + recipients, + train, + donor_weights=Weights(train.child_weight.to_numpy(), WeightKind.DESIGN), + match_columns=match_columns, + seed=seed, + ) + + def metrics(table: pd.DataFrame, weights: np.ndarray) -> dict[str, float]: + days = table[US_CHILDCARE_ATTENDANCE_COLUMNS[1]].to_numpy(dtype=float) + hours = table[US_CHILDCARE_ATTENDANCE_COLUMNS[2]].to_numpy(dtype=float) + return { + "participation": float(np.average(days > 0, weights=weights)), + "days_per_week": float(np.average(days, weights=weights)), + "hours_per_week": float(np.average(days * hours, weights=weights)), + } + + comparisons = [] + for grouping in (None, "age", "region", "parent_work_status"): + groups = ( + [("all", observed.index)] + if grouping is None + else observed.groupby(grouping).groups.items() + ) + for label, indices in groups: + weights = observed.loc[indices, "child_weight"].to_numpy() + comparisons.append( + { + "grouping": grouping or "all", + "group": str(label), + "n": len(indices), + "observed": metrics(observed.loc[indices], weights), + "predicted": metrics(predicted.loc[indices], weights), + } + ) + attrition = [ + { + "status": str(status), + "n": len(group), + "child_weight": float(group.child_weight.sum()), + } + for status, group in children.groupby("attendance_status", sort=True) + ] + return { + "source": source.source_receipt, + "source_child_count": len(children), + "source_attrition": attrition, + "under13_complete_weight_share": complete_mass / domain_mass, + "seed": seed, + "evaluation_design": "fixed household diagnostic split; not final population certification", + "match_columns": list(match_columns), + "training_children": len(train), + "heldout_children": len(heldout), + "unsupported_heldout_children": int((~supported).sum()), + "unsupported_heldout_weight": float( + heldout.loc[~supported, "child_weight"].sum() + ), + "household_overlap": len( + set(train.source_household_id) & set(heldout.source_household_id) + ), + "comparisons": comparisons, + "production_ready": False, + "limitations": [ + "Only complete, unambiguous calendars enter this holdout; source selection remains unvalidated.", + "The diagnostic matches age and caller-specified covariates; it is not a nationally validated fitted model.", + "Sibling assignments, mixed providers, older-child care and scalar monthly conversion need validation.", + "No full Microcosm population build or state CCDF distribution is certified by this source-only report.", + ], + } diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py new file mode 100644 index 000000000..efb2d1871 --- /dev/null +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -0,0 +1,274 @@ +"""Synthetic source-code and Frame/export contracts; no NSECE microdata in CI.""" + +import json + +import numpy as np +import pandas as pd +import pytest +from pandas.testing import assert_frame_equal + +from microcosm.build.frame_checkpoint import ( + load_frame_checkpoint, + write_frame_checkpoint, +) +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CALENDAR_BLOCKS, + NSECE_CHILD_INDICES, + NSECE_PROVIDER_INDICES, + assert_childcare_attendance_exportable, + derive_nsece_childcare, + load_nsece_childcare, + nsece_childcare_calendar_columns, + nsece_childcare_household_columns, + nsece_childcare_validation_report, + with_us_nsece_childcare_attendance, +) +from microcosm.frame import US_SCHEMA, Frame, WeightKind, Weights + +MONTH, DAYS, HOURS = US_CHILDCARE_ATTENDANCE_COLUMNS + + +def _raw(n=1): + household = pd.DataFrame( + -9.0, index=range(n), columns=nsece_childcare_household_columns() + ) + calendar = pd.DataFrame( + -9.0, index=range(n), columns=nsece_childcare_calendar_columns() + ) + for table in (household, calendar): + table["HH4_METH_CASEID"] = np.arange(1, n + 1) + household["HH4_METH_QUEXVERSION"] = 1 + household["HH4_REGION"] = 1 + household["HH4_PARWORK_STATUS"] = 2 + household["HH4_ECON_INCOME_ANNUAL"] = 40_000 + for child in NSECE_CHILD_INDICES: + household[f"HHC4_METH_WEIGHT_{child}"] = np.nan + household[f"HH4_MISSING_STATUS_CC_{child}"] = 0 + household["HHC4_AGE_AT_USAGE_1"] = 36 + household["HHC4_METH_WEIGHT_1"] = 100.0 + household["HH4_MISSING_STATUS_CC_1"] = 2 + for provider in NSECE_PROVIDER_INDICES: + household[f"HH4_TYPEOFCARE_AGG_1_{provider}"] = -8 + household["HH4_TYPEOFCARE_AGG_1_1"] = 4 + for block in range(1, NSECE_CALENDAR_BLOCKS + 1): + calendar[f"HH4_CHCAL_R_1_{block}"] = 0 + return household, calendar + + +def _care(calendar, *, row=0, day=0, hours=4, provider=1): + start = day * 96 + 9 * 4 + 1 + columns = [f"HH4_CHCAL_R_1_{b}" for b in range(start, start + hours * 4)] + calendar.loc[row, columns] = provider + + +def test_calendar_union_excludes_school_and_handles_multiple_providers(): + hh, cal = _raw() + hh["HH4_TYPEOFCARE_AGG_1_2"] = 6 # K-8 school is not ECE attendance. + hh["HH4_TYPEOFCARE_AGG_1_3"] = 3 # Unpaid care is still ECE. + _care(cal, day=0, hours=4) + _care(cal, day=1, hours=2, provider=3) + _care(cal, day=2, hours=6, provider=2) + before = (hh.copy(deep=True), cal.copy(deep=True)) + source = derive_nsece_childcare(hh, cal) + child = source.children.iloc[0] + assert child[DAYS] == 2 + assert child[HOURS] == 3 + assert child[MONTH] == 9 + assert child.ece_hours_per_week == 6 + assert child.ece_provider_count == 2 + assert source.weights.values.tolist() == [100] + assert_frame_equal(hh, before[0]) + assert_frame_equal(cal, before[1]) + + +@pytest.mark.parametrize( + "status,code,expected", + [ + (0, 0, "missing_calendar"), + (1, 0, "partial_calendar"), + (2, -1, "ambiguous_calendar"), + (2, 97, "ambiguous_calendar"), + (2, 77, "ambiguous_calendar"), + ], +) +def test_missing_or_ambiguous_calendar_never_becomes_observed_zero( + status, code, expected +): + hh, cal = _raw() + hh["HH4_MISSING_STATUS_CC_1"] = status + cal.loc[0, "HH4_CHCAL_R_1_1"] = code + source = derive_nsece_childcare(hh, cal) + assert source.children.attendance_status.tolist() == [expected] + assert source.children[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].isna().all().all() + + +def test_complete_parental_care_is_a_weighted_zero_donor(): + hh, cal = _raw() + source = derive_nsece_childcare(hh, cal) + donors, weights = source.donors() + assert donors[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].to_numpy().tolist() == [ + [0, 0, 0] + ] + assert weights.total == 100 + + +def test_unknown_provider_type_is_not_a_zero_schedule(): + hh, cal = _raw() + _care(cal) + hh["HH4_TYPEOFCARE_AGG_1_1"] = 8 + source = derive_nsece_childcare(hh, cal) + assert source.children.attendance_status.tolist() == ["ambiguous_calendar"] + + +def test_age_in_months_and_calendar_join_are_not_row_order_dependent(): + hh, cal = _raw(2) + hh.loc[1, "HHC4_AGE_AT_USAGE_1"] = 156 + _care(cal) + source = derive_nsece_childcare(hh, cal.iloc[::-1]) + assert source.children.age.tolist() == [3, 13] + assert source.children.attendance_status.tolist() == [ + "complete", + "age_out_of_scope", + ] + assert source.children.loc[0, DAYS] == 1 + + +def test_mismatched_or_duplicate_household_ids_raise(): + hh, cal = _raw(2) + cal.loc[0, "HH4_METH_CASEID"] = 3 + with pytest.raises(ValueError, match="ID sets"): + derive_nsece_childcare(hh, cal) + cal.loc[0, "HH4_METH_CASEID"] = 2 + with pytest.raises(ValueError, match="unique"): + derive_nsece_childcare(hh, cal) + + +def test_loader_refuses_unpinned_source(tmp_path): + path = tmp_path / "source.tsv" + path.write_text("wrong file\n") + with pytest.raises(ValueError, match="hash mismatch"): + load_nsece_childcare(path, path) + + +def _frame(*, parent_observed=True): + person = pd.DataFrame( + {"person_id": [1, 2], "person_source_id": ["adult", "child"], "age": [30, 3]} + ) + tables = {} + for entity in US_SCHEMA.group_entities: + person[f"person_{entity}_id"] = 1 + tables[entity] = pd.DataFrame({f"{entity}_id": [1]}) + if parent_observed: + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + person[column] = [0.0, np.nan] + tables["person"] = person + return Frame( + tables, + US_SCHEMA, + {"household": Weights(np.array([100.0]), WeightKind.DESIGN)}, + metadata={"existing_receipt": "preserved"}, + ) + + +def _source(): + hh, cal = _raw() + for day in range(5): + _care(cal, day=day, hours=8) + return derive_nsece_childcare(hh, cal) + + +def test_candidate_frame_preserves_structure_weights_and_checkpoint_receipts(tmp_path): + frame = _frame() + before = frame.table("person").copy(deep=True) + candidate = with_us_nsece_childcare_attendance( + frame, _source(), seed=915, match_columns=("age",) + ) + assert_frame_equal(frame.table("person"), before) + assert candidate.table("person")[DAYS].tolist() == [0, 5] + assert candidate.metadata["existing_receipt"] == "preserved" + assert candidate.metadata["nsece_childcare_attendance"]["candidate_only"] is True + np.testing.assert_array_equal( + candidate.weights_for("household").values, frame.weights_for("household").values + ) + for entity in US_SCHEMA.group_entities: + assert_frame_equal(candidate.table(entity), frame.table(entity)) + path = tmp_path / "candidate.h5" + receipts = json.loads(json.dumps(candidate.metadata, default=dict)) + write_frame_checkpoint(path, candidate, metadata={"frame_metadata": receipts}) + stored = load_frame_checkpoint(path) + reloaded = load_frame_checkpoint( + path, frame_metadata=stored.metadata["frame_metadata"] + ).frame + # Checkpoint JSON sorts mapping keys; receipts retain the same content. + assert json.loads(json.dumps(reloaded.metadata, default=dict)) == receipts + assert_frame_equal(reloaded.table("person"), candidate.table("person")) + + +def test_candidate_export_refuses_unresolved_out_of_scope_inputs(): + candidate = with_us_nsece_childcare_attendance( + _frame(parent_observed=False), _source(), seed=915, match_columns=("age",) + ) + assert pd.isna(candidate.table("person").loc[0, DAYS]) + with pytest.raises(ValueError, match="unresolved"): + assert_childcare_attendance_exportable(candidate) + + +@pytest.mark.requires_us +def test_real_engine_export_reload_preserves_child_attendance(tmp_path): + from policyengine_us import Microsimulation + from policyengine_us.data import USSingleYearDataset + + from microcosm.frame.adapters.policyengine_us import PolicyEngineUSEngine + + candidate = with_us_nsece_childcare_attendance( + _frame(), _source(), seed=915, match_columns=("age",) + ) + assert_childcare_attendance_exportable(candidate) + # Project canonical engine inputs; keep source provenance in the checkpoint. + tables = {e: candidate.table(e).copy() for e in candidate.entities} + tables["person"] = tables["person"].drop( + columns=[ + "person_source_id", + *(f"{c}_source" for c in US_CHILDCARE_ATTENDANCE_COLUMNS), + ] + ) + projected = Frame( + tables, + candidate.schema, + {"household": candidate.weights_for("household")}, + candidate.strata, + mass_log=candidate.mass_log, + metadata=candidate.metadata, + ) + path = tmp_path / "engine.h5" + PolicyEngineUSEngine().write_dataset(projected, path, period=2026) + dataset = USSingleYearDataset(file_path=str(path)) + assert dataset.person[MONTH].tolist() == [0, 22] + assert dataset.person[HOURS].tolist() == [0, 8] + sim = Microsimulation(dataset=dataset) + assert sim.calculate("childcare_hours_per_week", 2026).tolist() == [0, 40] + + +def test_validation_holds_out_whole_households_and_reports_exclusions(monkeypatch): + import microcosm.build.us_runtime.nsece_childcare as module + + hh, cal = _raw(30) + hh.loc[29, "HH4_MISSING_STATUS_CC_1"] = 0 + for row in range(29): + _care(cal, row=row) + source = derive_nsece_childcare(hh, cal) + original = module.impute_us_childcare_attendance + + def checked(recipient, donor, **kwargs): + assert set(recipient.source_household_id).isdisjoint(donor.source_household_id) + return original(recipient, donor, **kwargs) + + monkeypatch.setattr(module, "impute_us_childcare_attendance", checked) + report = nsece_childcare_validation_report(source) + assert report["household_overlap"] == 0 + assert report["production_ready"] is False + assert report["under13_complete_weight_share"] == pytest.approx(29 / 30) + assert report["comparisons"][0]["observed"] == report["comparisons"][0]["predicted"] diff --git a/packages/microcosm-build/tests/test_us_spine_blindness.py b/packages/microcosm-build/tests/test_us_spine_blindness.py index 3e24d7950..740761dc2 100644 --- a/packages/microcosm-build/tests/test_us_spine_blindness.py +++ b/packages/microcosm-build/tests/test_us_spine_blindness.py @@ -254,6 +254,8 @@ "medicaid_take_up.py", "misc_itemized.py", "nonzero_shares.py", + # Hash-verified local NSECE source adapter and opt-in candidate Frame step. + "nsece_childcare.py", "operator_boundary.py", # Raw-stage validator; no population treatment. "org_wages.py", "parity_reference.py", diff --git a/tools/prepare_us_childcare_attendance.py b/tools/prepare_us_childcare_attendance.py new file mode 100644 index 000000000..b14bc6bbb --- /dev/null +++ b/tools/prepare_us_childcare_attendance.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Prepare a local NSECE attendance candidate and aggregate validation report. + +No source downloads, remote uploads or release publication occur. Download the +DS4/DS5 TSVs under the ICPSR terms first. A Frame checkpoint is optional; source +validation is useful before a population candidate is available. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import platform +from importlib.metadata import version +from pathlib import Path + +from microcosm.build.frame_checkpoint import ( + load_frame_checkpoint, + write_frame_checkpoint, +) +from microcosm.build.us_runtime import childcare_attendance, nsece_childcare +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_MATCH_COLUMNS, + load_nsece_childcare, + nsece_childcare_validation_report, + with_us_nsece_childcare_attendance, +) +from microcosm.frame import Frame + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + parser.add_argument("--seed", type=int, default=915) + parser.add_argument( + "--match-columns", nargs="+", default=list(NSECE_CHILDCARE_MATCH_COLUMNS) + ) + parser.add_argument("--input-checkpoint", type=Path) + parser.add_argument("--output-checkpoint", type=Path) + args = parser.parse_args() + if bool(args.input_checkpoint) != bool(args.output_checkpoint): + parser.error( + "--input-checkpoint and --output-checkpoint must be supplied together" + ) + outputs = [p for p in (args.report, args.output_checkpoint) if p is not None] + if any(p.exists() for p in outputs): + parser.error( + "Output paths must be new; existing artifacts will not be overwritten" + ) + if len({p.resolve() for p in outputs}) != len(outputs): + parser.error("Report and checkpoint paths must differ") + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + report = nsece_childcare_validation_report( + source, seed=args.seed, match_columns=tuple(args.match_columns) + ) + report["candidate_frame_written"] = False + report["environment"] = { + package: version(package) + for package in ("numpy", "pandas", "microcosm-build", "microcosm-frame") + } + report["environment"]["python"] = platform.python_version() + report["code_sha256"] = { + path.name: _sha256(path) + for path in ( + Path(__file__), + Path(childcare_attendance.__file__), + Path(nsece_childcare.__file__), + ) + } + try: + if args.input_checkpoint is not None: + original = load_frame_checkpoint(args.input_checkpoint) + # The checkpoint protocol deliberately stores Frame receipts as + # external metadata; restore them explicitly rather than dropping + # the parent build's source/ownership evidence. + parent = original.frame + parent = Frame( + {entity: parent.table(entity) for entity in parent.entities}, + parent.schema, + { + entity: parent.weights_for(entity) + for entity in parent.weighted_entities + }, + parent.strata, + mass_log=parent.mass_log, + metadata={ + **parent.metadata, + **original.metadata.get("frame_metadata", {}), + }, + ) + candidate = with_us_nsece_childcare_attendance( + parent, + source, + seed=args.seed, + match_columns=tuple(args.match_columns), + ) + write_frame_checkpoint( + args.output_checkpoint, + candidate, + metadata={ + "artifact_kind": "nsece_childcare_candidate", + "childcare_candidate_only": True, + "parent_checkpoint_metadata": original.metadata, + "parent_checkpoint_sha256": _sha256(args.input_checkpoint), + "frame_metadata": json.loads( + json.dumps(candidate.metadata, default=dict) + ), + }, + ) + report["candidate_frame_written"] = True + except Exception as exc: + report["candidate_error"] = f"{type(exc).__name__}: {exc}" + raise + finally: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + print(f"Validation report: {args.report}") + print("Candidate only; production readiness is not certified.") + + +def _sha256(path: Path) -> str: + with path.open("rb") as stream: + return hashlib.file_digest(stream, "sha256").hexdigest() + + +if __name__ == "__main__": + main() From 651b69baa07bcadedcaf63c2948a7c3c10c92be8 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sat, 12 Sep 2026 23:06:23 -0400 Subject: [PATCH 04/16] Integrate NSECE attendance into US fiscal builds and qualify population --- docs/us-childcare-attendance.md | 359 ++-- experiments/us-childcare-attendance/README.md | 180 +- .../qualified-population-comparison.json | 1370 +++++++++++++++ .../qualified-preparation.json | 1476 +++++++++++++++++ .../qualified-remaining-state-inputs.json | 156 ++ .../build/us/childcare_attendance_source.json | 22 + .../microcosm/build/us/country_package.json | 5 + .../us/release_input_coverage_manifest.json | 18 +- .../build/us_runtime/childcare_attendance.py | 88 +- .../us_runtime/childcare_attendance_stage.py | 220 +++ .../build/us_runtime/childcare_population.py | 213 +++ .../build/us_runtime/nsece_childcare.py | 91 +- .../us_runtime/nsece_childcare_assessment.py | 292 ++++ .../us_runtime/nsece_childcare_bridge.py | 124 ++ .../us_runtime/nsece_childcare_dependence.py | 80 + .../us_runtime/release_input_coverage.py | 12 +- .../tests/test_us_fiscal_refresh_builder.py | 16 + .../tests/test_us_nsece_childcare.py | 376 +++++ .../tests/test_us_spine_blindness.py | 5 + .../calibrate/geography_constants.py | 35 + tools/build_us_fiscal_refresh_release.py | 44 + ...uild_us_release_input_coverage_manifest.py | 11 +- tools/generate_us_bundle_from_constants.py | 4 +- tools/prepare_us_childcare_attendance.py | 185 ++- tools/validate_us_childcare_population.py | 211 +++ 25 files changed, 5265 insertions(+), 328 deletions(-) create mode 100644 experiments/us-childcare-attendance/qualified-population-comparison.json create mode 100644 experiments/us-childcare-attendance/qualified-preparation.json create mode 100644 experiments/us-childcare-attendance/qualified-remaining-state-inputs.json create mode 100644 packages/microcosm-build/src/microcosm/build/us/childcare_attendance_source.json create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_assessment.py create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py create mode 100644 tools/validate_us_childcare_population.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index ef77c8934..80562f711 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -1,224 +1,175 @@ -# Child-care attendance inputs: source-backed preparation +# Child-care attendance: NSECE source and population integration Related: [Microcosm #915](https://github.com/PolicyEngine/microcosm/issues/915). -## Status and boundary - -`microcosm.build.us_runtime.childcare_attendance.impute_us_childcare_attendance` -is an **opt-in preparation primitive**. The verified NSECE source adapter and -`with_us_nsece_childcare_attendance` now support a local candidate `Frame` and -checkpoint flow, with aggregate diagnostics on the downloaded survey. Neither -operation is registered in the default US production pipeline. This change does -not populate published datasets or resolve #915. It does not change PolicyEngine-US defaults, -the engine ABI, or the existing SPM-unit childcare expense stage. - -The three outputs belong on the **person records of children receiving care**: - -- `childcare_attending_days_per_month` (integer) -- `childcare_days_per_week` -- `childcare_hours_per_day` - -These are YEAR-defined engine inputs describing attendance frequency, not annual -totals. `childcare_hours_per_week` remains derived by the engine from days times -hours. Do not put the child's schedule on parents or replicate it to every -household member. Separate attendance from CCDF eligibility, enrollment, and -subsidy receipt. Zero household expenses do not establish nonattendance, and -positive expenses do not establish a full-time schedule. - -## Implemented donor contract - -The function takes a person table, a normalized child donor table, positional -typed `Weights`, a seed, and exact matching columns including whole-year age. -Donors require unique canonical string `donor_id` values, complete matching -fields, and all three attendance inputs. Source normalization must remove -survey-specific missing codes; unknown attendance must not become zero. -Nonparticipants must be present as measured three-zero records with their -survey weights. The current donor domain is ages 0–12, matching the proposed -NSECE source scope, **not** a universal CCDF age-eligibility rule. - -The function draws a complete schedule with probability proportional to donor -survey weight among compatible donors. Thus participation and intensity remain -joint rather than making three independent predictions. Observed cells, -including zero, constrain the donor match and are never overwritten. Incomplete -or contradictory records, duplicate donors, missing matching fields, and empty -positive-weight matching cells raise errors. There is no fallback to a full-time -schedule or silent broadening of a matching cell. A fitted conditional model or -explicit sparse-cell strategy can later provide support without changing the -preservation contract. - -Recipients use canonical string `person_source_id` identifiers. Clones must have -identical matching fields and compatible observations. A stable hash of seed -and source person selects one donor for all clones; donor sorting makes the -selection independent of input order. Complete observations on a clone can fill -the others without donor support. All clones of a source person must be processed -together. Distinct siblings are not forced to share a schedule; joint household -matching remains a requirement to evaluate before activation. - -Every output has a companion `_source` column identifying observations, -donor assignments, or propagation from another copy of the source person. -Missing values outside the age domain remain null, while observed older-child -or adult values are retained. This is an intermediate table: it must **not** be -sent to the engine as a complete dataset until unresolved values are addressed -and the source coverage and production contracts below are satisfied. Provenance must be retained in build artifacts -and excluded from the engine input projection. - -## Verified source adapter +The US fiscal refresh builder can now produce three **person-level** inputs: +`childcare_attending_days_per_month`, `childcare_days_per_week`, and +`childcare_hours_per_day`. The source model covers children ages 0–12. It does +not change PolicyEngine-US defaults. Attendance, subsidy eligibility, provider +pricing, and benefit receipt are separate concepts. -The [2024 NSECE V1 release](https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation) -contains public household (DS5) and calendar (DS4) files. Both were downloaded -under the ICPSR agreement for this work. Each has 6,403 household records. The -loader verifies exact TSV SHA-256 hashes before parsing; hashes and byte counts -are recorded in the validation report. Raw records, donor tables, and source -archives remain local and are not included in the repository or CI fixtures. +The build runs `with_us_childcare_attendance_inputs` after the childcare expense +producer and before release validation. The generated release input contract +requires all three attendance columns. Licensed local source paths are explicit +build inputs; CI does not download or redistribute survey records. This PR +provides build integration and a qualified local population candidate; it does +not publish a replacement population or certify national CCDF spending. + +## Source and mapping -The mapping follows the Household Data Files User's Guide, printed pages HH-57, -HH-81, HH-279–280, HH-334, HH-554, and HH-565–568: +The [2024 NSECE V1 release](https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation) +contains household DS5 and calendar DS4 TSVs. Each contains 6,403 households. +The loader verifies the exact source hashes in `us/childcare_attendance_source.json` +before parsing. This separate `SourceStageSpec` compatibility resource preserves +the byte-frozen generation-0 source manifest. It contains declarative operations, +source pins, income bands, and price-year conventions. -| Source field | Adapter meaning | +| Field | Meaning | | --- | --- | -| `HH4_METH_CASEID` | One-to-one household/calendar join; child suffixes agree across files | -| `HHC4_AGE_AT_USAGE_X` | Reference-week age in months; divide by 12 and floor; -9 means no child | -| `HHC4_METH_WEIGHT_X` | Child design weight, positive for present children | -| `HH4_MISSING_STATUS_CC_X` | 0 missing calendar, 1 partial, 2 complete | +| `HH4_METH_CASEID` | One-to-one household/calendar join | +| `HHC4_AGE_AT_USAGE_X` | Child age in months in the reference week | +| `HHC4_METH_WEIGHT_X` | Child design weight for donor draws | +| `HH4_METH_WEIGHT` | Household design weight for sibling dependence | +| `HH4_MISSING_STATUS_CC_X` | Missing, partial, or complete calendar | | `HH4_CHCAL_R_X_Z` | 672 successive 15-minute blocks, starting Monday midnight | -| `HH4_TYPEOFCARE_AGG_X_Y` | Provider type for child X and provider Y | -| `HH4_REGION` | Four Census regions, not observed state | -| `HH4_PARWORK_STATUS` | Parents of any household child: -1 no parents, 0 none worked, 1 some worked, 2 all worked | -| `HH4_METH_QUEXVERSION` | Main, summer, or new-school-year questionnaire; retained for diagnostics | -| `HH4_ECON_INCOME_ANNUAL` | Annual household income for 2023; retained, not currently matched | - -ECE includes individual paid/unpaid care, centers, other organizations, and -irregular arrangements (types 1–5 and 7). K–8 schooling (type 6) is excluded. -The calendar already identifies one final provider per block. Attendance is the -union of ECE blocks: days count days with any ECE, and hours per day equal total -weekly ECE hours divided by those days. Multiple providers are counted for -subsequent diagnostics, but this does not solve provider-specific pricing. - -Monthly days use `floor(days_per_week * 52 / 12 + 0.5)`: five weekly days map to -22 monthly days. This is an explicit representative-week approximation, not an -observed month or an engine default. Summer and new-school-year questionnaires -lack the required calendar; weekly-hours summaries alone cannot recover days. - -**Missing calendars must not become observed zeros.** The guide warns that some -summary variables encode absent calendars as parental-only care. The adapter -requires a complete calendar and classifiable blocks. Unknown provider types, -missing blocks, and ambiguous gap-check codes leave all three inputs null with -an exclusion reason. A complete parental/self-care or school-only calendar is a -valid weighted zero donor. Unsupported codes are never guessed. - -## Candidate integration and reproduction - -Run locally after obtaining both TSVs under the archive's terms: +| `HH4_TYPEOFCARE_AGG_X_Y` | Child/provider care type | +| `HH4_RPARENT` | Whether respondent care is parental care | +| `HH4_REGION` | Census region | +| `HH4_PARWORK_STATUS` | Work status of parents of any under-13 household child | +| `HH4_METH_QUEXVERSION` | Main, summer/typical-May, or new-school-year instrument | +| `HH4_ECON_INCOME_ANNUAL` | Published household pretax income for 2023 | +| `HHC4_NPC_HRSWEEK_TOC1..5_X` | Regular-care weekly hours for the noncalendar bridge | + +Regular ECE includes provider types 1–5; type 7 is irregular care. K–8 schooling +(type 6) is excluded. Certain unpaid-care gap codes count as ECE. Respondent +care depends on `HH4_RPARENT`; school gap code 68 is classifiable as non-ECE only +at age six or older. Ambiguous codes remain unknown. A complete parental, +self-care, or school-only calendar is a measured zero donor. Missing calendars +never become observed zeros. + +Attendance uses the union of classified ECE blocks. Days count days with any +ECE; hours per day equal weekly ECE hours divided by days. Monthly days use +`floor(days_per_week * 52 / 12 + 0.5)` (five days becomes 22). This represents a +typical week, not an observed month or a provider-specific schedule. + +## Noncalendar reconstruction and joint transfer + +Summer and new-school-year instruments have no calendars. The summer instrument +refers to a typical May week; this is **not measured summer attendance**. The +bridge preserves their published regular weekly hours and borrows days and +irregular hours jointly from ten nearest complete-calendar donors (including all distance ties), using +log regular hours, matching covariates, and survey weights. Regular-care +participation must agree. Zero regular hours does not establish zero irregular +care. Bridged rows are labeled `summary_bridge`, never `complete`. + +Default matching uses age, Census region, parent work, and household income band. +The declared sparse-cell hierarchy drops region, then income, then parent work; +age is always retained. Chosen levels are recorded. Empty support or incompatible +observations fail; there is no invented full-time schedule. + +The imputer draws a complete schedule jointly with child survey weights. +Observed cells, including observed zeros, constrain matching and are preserved. +Stable source person IDs keep clones identical and assignments independent of +row order. The native adapter losslessly encodes integer IDs temporarily. +A fitted mixture of independent child ranks and a shared household rank models +sibling dependence while preserving each child's conditional donor distribution. +It is fitted on youngest sibling pairs in fully observed households using +household weights and evaluated with household-separated folds. + +## ASEC target harmonization + +`harmonize_asec_childcare_predictors` resolves `PEPAR1` and `PEPAR2` against +`A_LINENO` within physical households. It counts measured last-week work among +parents of any under-13 household child, matching the NSECE unit. Unrelated +working adults do not become parents. Dangling parent pointers fail. + +Regions derive from the shared Census state mapping. Household income is the +sum of raw `PTOTVAL`, expressed in 2023 dollars using annual CPI-U. The pinned +BuildP parent omits raw income for its 2022/2023 source cohorts. The optional +ASEC cache recovers a temporary income array from the existing pinned Census +archives, joining exact 22-digit `PERIDNUM` plus source year and checking raw +age, line number, and any already observed income. Original population columns, +including raw missingness, remain unchanged. No downloader runs inside the stage. + +## Build and reproduction + +Obtain the two ICPSR TSVs under the archive's terms and the pinned ASEC CSVs in +`education_assistance_source.py` (its existing fetch helper verifies them). +Run the complete local candidate and source diagnostics: ```bash uv sync --all-packages --locked --extra us uv run python tools/prepare_us_childcare_attendance.py \ --household-tsv /local/39466-0005-Data.tsv \ --calendar-tsv /local/39466-0004-Data.tsv \ - --report /local/nsece-validation.json + --asec-population-h5 /local/populace_us_2024.h5 \ + --population-sha256 48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e \ + --asec-source-cache /local/asec \ + --production-stage --extended-assessment \ + --inherit-outside-domain-baseline \ + --output-checkpoint /local/attendance-checkpoint.h5 \ + --output-native-h5 /local/attendance-candidate.h5 \ + --report /local/preparation.json ``` -To apply to a local US `Frame` checkpoint, add -`--input-checkpoint /local/parent.h5 --output-checkpoint /local/candidate.h5`. -The output paths must be new. The operation preserves entity links, row order, -weights, strata, mass history, and inherited metadata. Source hashes, matching -columns, seed, and a candidate-only receipt accompany the checkpoint; the report -also records environment versions and code hashes. It neither publishes a -release nor updates production manifests. - -The target person table must already contain canonical `person_source_id` plus -harmonized `age`, `region`, and `parent_work_status`. These are the default exact -matching columns. Parent work must be derived from actual parent relationships, -not by classifying every adult as a parent. This CLI does not yet perform that -harmonization for production population sources. `--match-columns` allows -explicit experiments; age is mandatory. Existing known attendance, including -zero, is preserved. The caller must distinguish genuinely observed zeros from -previously materialized engine defaults before invoking the operation. - -Unknown attendance outside ages 0–12 remains null. Before an engine export, -`assert_childcare_attendance_exportable` requires all three columns to be finite -for every person; it does not certify representativeness. Retain provenance in -the checkpoint and project only canonical inputs to the engine. A synthetic -integration test exports a `Frame`, reloads it through `USSingleYearDataset`, and -verifies child weekly hours in a real `Microsimulation`. The CLI was also run -with the real source and a synthetic target checkpoint; this establishes data -flow, not population validity. - -## Actual source diagnostics - -See [the recorded experiment](../experiments/us-childcare-attendance/README.md) -and its aggregate JSON. Of 11,745 source children, 134 are outside ages 0–12. -There are 7,120 complete, unambiguous schedules, covering **60.26%** of weighted -under-13 children. Another 3,095 have missing calendars, 174 partial calendars, -and 1,222 ambiguous calendars. Original survey weights do not by themselves -make this selected subset nationally representative. - -A deterministic household split holds out 1,399 children and trains on 5,721; -no household occurs on both sides. Matching age, region, and parent work leaves -three held-out children unsupported, explicitly excluded from scoring. Among -1,396 supported children, weighted participation is **44.82% observed versus -45.99% imputed**; average weekly days are 1.82 versus 1.79 and weekly hours -13.65 versus 13.09. These are means across participants and nonparticipants. - -Age-and-region-only matching initially substantially understated attendance for -all-working-parent households and overstated it for some-working-parent -households. Adding parent work improves those comparisons, but the same split -was reused in development. It is a diagnostic, not an untouched final test set. -Subgroup differences, sampling uncertainty, missing-calendar selection, and -population transfer remain unresolved. The report explicitly records -`production_ready: false`. - -## Remaining production acceptance work - -1. Address calendar selection and seasonality using source design/response - evidence; quantify differences between included and excluded children and - validate a response adjustment or complementary source. Retain unknowns as - unknowns. Do not simply reuse the selected sample's weights as national totals. -2. Implement and test source-specific target harmonization for parent links, - work/activity, region, school status, income year, and stable source IDs. - Measure target matching support. Specify and validate a sparse-cell model - rather than silently broadening failed exact matches. -3. Evaluate joint household assignments for sibling coherence, mixed-provider - schedules against the engine's scalar provider inputs, and the monthly - conversion against consuming formulas. Resolve older children, including - disability-related care, and other out-of-domain records before export. -4. Register the operation and late producer through normal generation tools; - update source specs, generated manifests, coverage and producer inventories, - and engine input contracts. Verify build ordering on a real parent artifact. - The opt-in candidate path here is not that default-build activation. -5. Run a full candidate population with pinned parent/engine/code identities. - Predeclare validation criteria, use fresh evaluation data or cross-validation, - quantify uncertainty, and inspect participation and days/hours distributions - by age, income, work pattern, geography, and provider. Compare state CCDF - outcomes before/after; synthetic positive-benefit examples cannot certify - those population estimates. - -Provider and activity inputs need their own evidence. Earlier diagnostic -households required explicit MA/MD provider types and Nevada's activity input -after attendance was supplied; populating attendance alone is not a complete -CCDF data solution. Likewise, -[PolicyEngine-US #9405](https://github.com/PolicyEngine/policyengine-us/issues/9405) -concerns which state benefits enter the household aggregate and is independent -of this source preparation. - -## Local checks +All output paths must be new. The checkpoint retains per-cell provenance, +matching levels, source and parent receipts, weights, strata, and mass history. +Native export adds only the three inputs and a receipt, reloads the result, and +verifies every original entity column, household weight, and time period. +Code hashes and environment versions accompany the aggregate preparation report. + +Supply the same source inputs to the normal fiscal build using +`--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, +`--childcare-attendance-asec-cache`, and +`--childcare-attendance-inherit-outside-domain-baseline` alongside its usual flags. +Source receipts enter the fiscal source-coverage report. The frozen pool ABI +continues to describe the earlier pool simulation; attendance is supplied by this +subsequent fiscal-build stage and enforced by the final release input contract. +Existing release input +gates still apply; a build without required attendance inputs cannot substitute +an engine default for a persisted input. + +Unknown values outside ages 0–12 stay null in the source model. The explicit +outside-domain export policy fills only these missing cells with the pinned +engine's existing baseline and labels them `inherited_engine_baseline_outside_age_0_12`. +This permits an attendance-only candidate without asserting observed +nonattendance for teens or disability-related older-child care. Observed older +values are preserved; unresolved under-13 values always fail export. + +Compare every state model against the exact parent: ```bash -uv sync --all-packages --locked --extra us -uv run pytest packages/microcosm-build/tests/test_us_childcare_attendance.py -uv run pytest packages/microcosm-build/tests/test_us_nsece_childcare.py -uv run pytest packages/microcosm-build/tests/test_us_spine_blindness.py -uv run ruff check . -uv run python tools/ci_test_groups.py --verify +uv run python tools/validate_us_childcare_population.py \ + --parent-h5 /local/populace_us_2024.h5 \ + --parent-sha256 48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e \ + --candidate-checkpoint /local/attendance-checkpoint.h5 \ + --year 2026 --report /local/population-comparison.json ``` -The donor tests use only explicitly synthetic records. They check survey-weighted -participation, joint schedules, preservation of observed zeros, source-clone -coherence, stable ordering/chunking, age scope, and rejection of unsupported or -invalid input. Production data validation remains separate from PR CI. - -The runtime inventory in `test_us_spine_blindness.py` explicitly classifies both -preparation modules outside the population-treatment registry. That classification -does not exempt it from the all-runtime source-spine access scan. Registering it -as a production stage must update its classification as well as the source and -coverage contracts above. +Both arms use fixed source ages and incomes without aging or uprating. Direct +state subsidy variables avoid conflating attendance with the separate household +aggregation issue [PolicyEngine-US #9405](https://github.com/PolicyEngine/policyengine-us/issues/9405). +Outputs describe potential modeled benefits, not caseload or spending estimates. + +## Validation and limits + +See [the aggregate experiment](../experiments/us-childcare-attendance/README.md). +Diagnostics include five-fold household cross-validation, instrument selection, +income/age/work/region comparisons, masked-calendar reconstruction, sibling joint +attendance, full target support, and all-state benefit comparisons. Development +used these diagnostics; they are not an untouched external acceptance sample. + +Calendar selection remains unidentifiable for excluded ambiguous/partial cases. +Conditional matching assumes their schedules resemble supported children with +similar covariates. Bridged schedules are modeled, despite observed regular hours. +Source weights and good predictive means do not establish national validity. +Provider-specific schedules, true summer care, and older-child attendance need +additional evidence. Attendance alone cannot fix other missing CCDF inputs. +The reports retain `production_ready: false` to distinguish this candidate from +a calibrated, independently reviewed and published population release. + +CI uses synthetic records only. Tests cover survey parsing, unknown/zero +separation, weighted joint draws, observed-cell and clone preservation, parent +links, verified income joins, noncalendar reconstruction, sibling ranks, build +orchestration, and native engine export. New runtime modules are classified in +the existing inventory and remain subject to the full source-spine AST guard. diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index f798e123e..9159609ba 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,89 +1,109 @@ -# NSECE attendance candidate: 2026-09-12 diagnostic +# NSECE attendance population qualification — 2026-09-12 Related: [#915](https://github.com/PolicyEngine/microcosm/issues/915) and -[draft PR #916](https://github.com/PolicyEngine/microcosm/pull/916). +[PR #916](https://github.com/PolicyEngine/microcosm/pull/916). -**Verdict: candidate only; not ready for production activation.** The real source -adapter and local checkpoint path work. This experiment does not establish -national representativeness, target population validity, or state CCDF effects. +The real survey adapter, ASEC harmonization, fiscal-builder attendance stage, +and native population export are implemented. The final aggregate reports below +record the source/model diagnostics and the attendance-only state comparison. +This is a local population candidate, not a calibrated or published replacement +population, and the reports retain `production_ready: false`. -The [aggregate report](nsece-2024-v1-validation.json) was generated from the -2024 NSECE public household DS5 and calendar DS4 TSVs, ICPSR 39466 V1. It records -source hashes/size, seed, exact code hashes, and Python/library versions. It -contains no individual records or identifiers. Source archives, normalized donor -records, and local candidate checkpoints are not redistributed in this PR. -See the [mapping and reproduction guide](../../docs/us-childcare-attendance.md). +- [Source and production-stage qualification](qualified-preparation.json) +- [All-state population comparison](qualified-population-comparison.json) +- [Reproduction and field mapping](../../docs/us-childcare-attendance.md) -## Source coverage +No individual survey records, identifiers, raw archives, or H5 populations are +committed. Reports contain aggregate diagnostics and exact artifact/code hashes. +The earlier [source-only report](nsece-2024-v1-validation.json) is historical +(f065a3ae), predating the corrected gap-code classification and population model. +It does not describe the final candidate. -| Status | Children | +## Source coverage and model + +| Source status | Children | | --- | ---: | -| Complete and unambiguous | 7,120 | -| Missing calendar | 3,095 | -| Partial calendar | 174 | -| Ambiguous calendar/provider | 1,222 | -| Age outside 0–12 | 134 | +| Complete classified calendars | 7,460 | +| Reconstructed from observed regular weekly hours | 3,046 | +| Still excluded under age 13 | 1,105 | +| Outside source age domain | 134 | | Total | 11,745 | -Complete schedules cover **60.26%** of the original weighted under-13 population. -This is source coverage, not an attendance participation rate. The original -child weights are used for conditional donor draws and descriptive comparisons; -using them after these exclusions does not validate national totals. Calendar -availability differs by questionnaire version, including the summer and -new-school-year instruments. Missing and ambiguous attendance stays unknown. - -## Household-separated diagnostic - -A stable hash with seed 915 holds out approximately 20% of households, keeping -siblings on the same side: 5,721 training children and 1,399 held-out children, -with zero household overlap. The final candidate matches exact child age, -Census region, and household parent-work status. Three held-out children lack -training support (weight 44,357.42) and are reported separately; 1,396 are scored. -No unsupported record is assigned a default zero. - -| Weighted mean, supported children | Observed | Imputed | -| --- | ---: | ---: | -| Any ECE attendance | 44.82% | 45.99% | -| Days per week | 1.823 | 1.793 | -| Hours per week | 13.652 | 13.095 | - -Means include participants and nonparticipants. Matching draws all three -attendance inputs jointly; it does not independently predict their means. - -| Parent work group | Observed participation | Imputed participation | -| --- | ---: | ---: | -| All parents worked | 60.79% | 59.14% | -| Some parents worked | 25.44% | 30.49% | -| No parents worked | 28.59% | 33.79% | -| No parents present, supported subset | 28.83% | 20.26% | - -An earlier age-and-region-only diagnostic on the same household split imputed -41.51% participation for all-working-parent households and 47.03% for -some-working-parent households, compared with observed 60.79% and 25.44%. -That motivated adding parent work. Consequently this split has been used during -model development and must not be treated as an untouched final acceptance set. -Age, region, and work-group detail is retained in the JSON; for example the -age-zero weekly-hours mean remains 9.85 imputed versus 13.48 observed. No -uncertainty intervals or acceptance thresholds have yet been established. - -## Integration evidence and limits - -The report's `candidate_frame_written: true` refers to a **two-person synthetic -target checkpoint with the real survey donor source**, not a production build. -The local CLI run preserved parent receipt metadata, entity links and household -weight, wrote a separate candidate checkpoint, and reloaded its attendance -columns without missing values for this fixture. The fixture's adult had -explicit observed zeros; the adapter did not infer adult zero attendance. - -A separate synthetic-source CI test exports through `PolicyEngineUSEngine`, -reloads through `USSingleYearDataset`, and calculates weekly hours through a -real `Microsimulation`. Locally this ran with PolicyEngine-US 1.819.0 and period -2026. It verifies the person input/export contract only. It does not validate -benefit changes or production data. - -The next acceptance work is source-selection/seasonality analysis, tested -harmonization of real target parent/work/region fields, sparse-cell handling, -sibling and provider conventions, older-child coverage, registered build and -input contracts, and a full population comparison with pinned parent and engine -artifacts. No release, production manifest change, or national CCDF result is -claimed here. +The 10,506 usable donors include measured nonparticipants and modeled schedules +for May/fall questionnaire respondents. The latter preserve regular weekly hours +but borrow days and irregular care; they are not observed full schedules. The +remaining excluded records comprise 882 ambiguous calendars, 174 partial +calendars, and 49 unusable noncalendar summaries. Conditional matching cannot +identify their missing schedules without additional assumptions. + +Matching uses age, region, parents' last-week work, and household income in 2023 +dollars. The explicit fallback hierarchy always retains age. Tied nearest-hour +donors are all retained. A household shared-rank mixture models sibling +participation; it does not assert common provider identity. + +The source evaluation uses five household-separated folds. Overall observed ECE +participation is 46.51%, compared with 46.55% predicted, and weekly hours are +14.24 observed versus 14.08 predicted. For youngest sibling pairs, joint +attendance is 32.86% observed versus 32.28% predicted; independent draws predict +25.99%. The masked-calendar check holds out 1,581 children by whole household: weekly +hours are 13.10 observed versus 13.16 reconstructed, and days are 1.72 versus +1.79. Subgroup discrepancies remain visible in the final report. These diagnostics informed development and +must not be described as an untouched external acceptance sample. + +## Population boundary + +The exact BuildP parent contains 166,321 people, 57,240 households, and 31,889 +children ages 0–12. The stage restores temporary income predictors for all three +ASEC cohorts from pinned Census sources while preserving the parent's original +columns, raw missingness, entity links, weights, and period. Its native export +adds only the three attendance inputs and a provenance receipt. + +Outside ages 0–12, the explicit export policy inherits existing engine baseline +values where observations are absent. The 134,432 out-of-domain people include +557 disabled teenagers ages 13–17. Their attendance has not been estimated by +this source. Preserving baseline behavior does not establish nonattendance. + +The state experiment applies 2026 policies to fixed source ages and incomes, +without aging or uprating, and calls each direct state child-care subsidy +variable. Only attendance changes. Provider, activity, expense, enrollment, and +take-up inputs remain as in the parent. Benefit amounts are potential modeled +benefits, not national CCDF spending or caseload estimates. Source-selection, +true summer schedules, provider-specific pricing, and older-child coverage +remain limitations for population publication. + + +## Final state results + +All 51 jurisdictions were evaluated. All-zero state results fell from **31 to 3**: +California, Maryland, and Nevada. Positive modeled subsidies became available +in 28 additional jurisdictions. The aggregate annual potential benefit changes +from $2.253 billion to $5.286 billion under this fixed-population experiment; +these amounts are not calibrated spending estimates. + +All 31,889 under-13 children have resolved inputs. Weighted attendance is 48.47%, +with 1.959 days and 14.081 hours per week averaged across all children, including +nonparticipants. Exact four-field support covers 31,152 children; 576 use +age/parent-work/income and 161 use age/parent-work. None needs the age-only level. +The report gives target distributions by age, region, work, and income. + +The [remaining-input diagnostic](qualified-remaining-state-inputs.json) identifies +separate blockers in January 2026: + +| State | Evidence on this candidate | +| --- | --- | +| CA | 341 SPM units meet CAPP eligibility, but state-specific days/month and weeks/month remain zero, making the time coefficient zero. | +| MD | 115 SPM units meet CCS eligibility, but every provider type is `NONE`, giving a zero reimbursement rate. | +| NV | 149 SPM units meet the income test, but every activity test is false. | + +These require additional source/engine mapping work. The PR does not infer +licensed provider status or approved CCDF activity from attendance alone, and +does not claim to close every part of #915. + +### Local checks + +916 distinct targeted tests passed across the attendance/source, architecture, +fiscal-builder, release-coverage, and US bundle suites. Lint, tracked CI test +inventory, generated bundle validation, and diff whitespace checks passed. +The real production stage completed on the pinned parent, and native export +reloaded successfully with original data, weights, and period preserved. +Full GitHub CI runs separately on the submitted commit. diff --git a/experiments/us-childcare-attendance/qualified-population-comparison.json b/experiments/us-childcare-attendance/qualified-population-comparison.json new file mode 100644 index 000000000..100da246c --- /dev/null +++ b/experiments/us-childcare-attendance/qualified-population-comparison.json @@ -0,0 +1,1370 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "candidate_checkpoint_sha256": "882c2ae6b683e2a04ce6c94f5f694f70750a8c2876e8108e3715a7f7095d1f66", + "validation_code_sha256": "91e5d7c5eb7a634a5518ee44bac4ae60a84cef86819bd01752375659339285f8", + "engine_version": "1.819.0", + "policy_year": 2026, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "people": 166321, + "households": 57240, + "under13_children": 31889, + "outside_source_domain_people": 134432, + "outside_source_domain_age13_17_with_disability": 557, + "outside_domain_baseline_receipt": { + "engine_version": "1.819.0", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "unresolved_people_retaining_baseline": 0, + "unresolved_age13_17_with_disability": 0, + "weighted_under13_attendance_share": 0.48474940604537897, + "weighted_under13_days_per_week": 1.9587696980712788, + "weighted_under13_hours_per_week": 14.080797907203973, + "candidate_receipt": { + "childcare_attendance_stage": { + "modeled_age_domain": [ + 0, + 12 + ], + "operation_order": [ + "derive_childcare_inputs", + "derive_childcare_inputs", + "derive_childcare_inputs" + ], + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "outside_domain_policy": "inherit_engine_baseline", + "seed": 915, + "sibling_dependence": { + "estimation": "youngest pair in fully observed households; household design weights", + "households": 1941, + "independent_both_in_care": 0.2585427028841666, + "observed_both_in_care": 0.32858869865910467, + "rho": 0.7413149380294699, + "shared_both_in_care": 0.3530315526045447, + "unconstrained_rho": 0.7413149380294699 + }, + "stage": "nsece_childcare_attendance" + }, + "childcare_outside_domain_baseline": { + "engine_version": "1.819.0", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "childcare_predictor_harmonization": { + "geography": "Census region from target state_fips", + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars", + "income_source_receipts": [ + { + "income_year": 2022, + "matched_people": 54464, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "source_rows": 146133 + }, + { + "income_year": 2023, + "matched_people": 54654, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "source_rows": 144265 + }, + { + "income_year": 2024, + "matched_people": 57203, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "source_rows": 142125 + } + ], + "parent_universe": "parents of any child age 0 through 12 in household", + "source": "ASEC resident parent line pointers", + "work_measure": "hours_worked_last_week > 0" + }, + "nsece_childcare_attendance": { + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "candidate_only": true, + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "noncalendar_bridge": { + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", + "completed_children": 3046, + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "observed": "regular weekly hours from May/fall questionnaire", + "seed": 915, + "unsupported_children": 0 + }, + "seed": 915, + "sibling_dependence": 0.7413149380294699, + "source_year": 2024, + "study": "ICPSR39466.v1" + } + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159527.697095435 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 336693781.677099 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 2554.6082648136944, + "annual_modeled_benefits": 15688867.198366832 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13277.069981029548, + "annual_modeled_benefits": 54542065.75748161 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 212.01572016369738 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 249203866.21813098 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.243193686 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + } + } + ], + "production_ready": false, + "interpretation": "attendance-only counterfactual; provider, activity, expenses and take-up inputs remain as in parent", + "matching_levels": { + "age,region,parent_work_status,income_band": 31152, + "age,parent_work_status,income_band": 576, + "age,parent_work_status": 161 + }, + "population_attendance_by_group": [ + { + "grouping": "age", + "group": "0.0", + "children": 1689, + "weighted_children": 2750700.861434371, + "attendance_share": 0.3982910388950054, + "days_per_week": 1.4637084569333976, + "hours_per_week": 12.47778129720075 + }, + { + "grouping": "age", + "group": "1.0", + "children": 2157, + "weighted_children": 3858818.186826853, + "attendance_share": 0.5828056228035874, + "days_per_week": 2.420690450933124, + "hours_per_week": 20.799764139530804 + }, + { + "grouping": "age", + "group": "2.0", + "children": 2364, + "weighted_children": 3754041.8444214575, + "attendance_share": 0.6245468848119025, + "days_per_week": 2.622672601643104, + "hours_per_week": 21.59076854224297 + }, + { + "grouping": "age", + "group": "3.0", + "children": 2385, + "weighted_children": 3880377.4871301977, + "attendance_share": 0.6440103162467938, + "days_per_week": 2.9239564940965894, + "hours_per_week": 21.837287866122146 + }, + { + "grouping": "age", + "group": "4.0", + "children": 2482, + "weighted_children": 4358831.600945832, + "attendance_share": 0.7298361168227924, + "days_per_week": 3.346378469862117, + "hours_per_week": 26.819455757918746 + }, + { + "grouping": "age", + "group": "5.0", + "children": 2391, + "weighted_children": 4095170.7360430276, + "attendance_share": 0.5643585836076381, + "days_per_week": 2.549062633648557, + "hours_per_week": 18.492995302361113 + }, + { + "grouping": "age", + "group": "6.0", + "children": 2562, + "weighted_children": 4029009.778184548, + "attendance_share": 0.4495509704841995, + "days_per_week": 1.6790815724045138, + "hours_per_week": 10.176170030770823 + }, + { + "grouping": "age", + "group": "7.0", + "children": 2612, + "weighted_children": 3768786.8623211696, + "attendance_share": 0.348461328136747, + "days_per_week": 1.4555085622939352, + "hours_per_week": 9.205290314751595 + }, + { + "grouping": "age", + "group": "8.0", + "children": 2689, + "weighted_children": 4419397.563846819, + "attendance_share": 0.41984770408640326, + "days_per_week": 1.582158698623752, + "hours_per_week": 9.889788909271815 + }, + { + "grouping": "age", + "group": "9.0", + "children": 2552, + "weighted_children": 3888389.065045069, + "attendance_share": 0.37587948153623024, + "days_per_week": 1.3231923573550826, + "hours_per_week": 8.472614481687524 + }, + { + "grouping": "age", + "group": "10.0", + "children": 2662, + "weighted_children": 4369978.335878741, + "attendance_share": 0.43521505778260355, + "days_per_week": 1.5068808517461778, + "hours_per_week": 8.205007778013812 + }, + { + "grouping": "age", + "group": "11.0", + "children": 2795, + "weighted_children": 4689133.259098214, + "attendance_share": 0.4034636828760653, + "days_per_week": 1.4253271874750526, + "hours_per_week": 8.173318571609755 + }, + { + "grouping": "age", + "group": "12.0", + "children": 2549, + "weighted_children": 4093051.641607744, + "attendance_share": 0.31020269689115165, + "days_per_week": 1.1064542406772742, + "hours_per_week": 7.828746644810994 + }, + { + "grouping": "region", + "group": "1", + "children": 5553, + "weighted_children": 7976838.3562431075, + "attendance_share": 0.5123696490920082, + "days_per_week": 2.208084285948932, + "hours_per_week": 15.123524314240859 + }, + { + "grouping": "region", + "group": "2", + "children": 7790, + "weighted_children": 10772583.599183697, + "attendance_share": 0.5153609964315923, + "days_per_week": 1.9523623395589054, + "hours_per_week": 14.70850903160164 + }, + { + "grouping": "region", + "group": "3", + "children": 10750, + "weighted_children": 21007309.669184513, + "attendance_share": 0.44174742437852166, + "days_per_week": 1.7895644362796808, + "hours_per_week": 13.033216088883725 + }, + { + "grouping": "region", + "group": "4", + "children": 7796, + "weighted_children": 12198955.598172724, + "attendance_share": 0.5137082633621989, + "days_per_week": 2.0927835451441785, + "hours_per_week": 14.648645297341831 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "children": 684, + "weighted_children": 1044954.9507498589, + "attendance_share": 0.5422109593087965, + "days_per_week": 2.2811995403367784, + "hours_per_week": 25.482061788407137 + }, + { + "grouping": "parent_work_status", + "group": "0", + "children": 2876, + "weighted_children": 5039737.2909418885, + "attendance_share": 0.3083075074659248, + "days_per_week": 1.283739352802779, + "hours_per_week": 11.283501114920222 + }, + { + "grouping": "parent_work_status", + "group": "1", + "children": 9966, + "weighted_children": 15397404.080512933, + "attendance_share": 0.3115897123620967, + "days_per_week": 0.9599810926770269, + "hours_per_week": 6.6244519255662295 + }, + { + "grouping": "parent_work_status", + "group": "2", + "children": 18363, + "weighted_children": 30473590.900579363, + "attendance_share": 0.5994515383571952, + "days_per_week": 2.5640085999146067, + "hours_per_week": 17.919932134788446 + }, + { + "grouping": "income_band", + "group": "0", + "children": 94, + "weighted_children": 81378.29635578688, + "attendance_share": 0.29040892685678327, + "days_per_week": 1.0780433457763277, + "hours_per_week": 6.3255131795754185 + }, + { + "grouping": "income_band", + "group": "1", + "children": 3165, + "weighted_children": 6927519.022630059, + "attendance_share": 0.4025097371077471, + "days_per_week": 1.6934463444760064, + "hours_per_week": 12.252209201740305 + }, + { + "grouping": "income_band", + "group": "2", + "children": 6097, + "weighted_children": 10396829.673697237, + "attendance_share": 0.4585389868464392, + "days_per_week": 1.9481109164610568, + "hours_per_week": 14.847718600455874 + }, + { + "grouping": "income_band", + "group": "3", + "children": 9010, + "weighted_children": 14071962.152827512, + "attendance_share": 0.45002653661522485, + "days_per_week": 1.7736004293258292, + "hours_per_week": 13.239778272162871 + }, + { + "grouping": "income_band", + "group": "4", + "children": 7428, + "weighted_children": 13192218.644710096, + "attendance_share": 0.5136530195198358, + "days_per_week": 1.9683914038135206, + "hours_per_week": 14.280673940380375 + }, + { + "grouping": "income_band", + "group": "5", + "children": 6095, + "weighted_children": 7285779.432563353, + "attendance_share": 0.6172477004274525, + "days_per_week": 2.576313155655513, + "hours_per_week": 16.074152133280744 + } + ], + "summary": { + "states_evaluated": 51, + "all_zero_states": { + "baseline": [ + "AL", + "AZ", + "AR", + "CA", + "DC", + "FL", + "HI", + "ID", + "IL", + "IA", + "KS", + "KY", + "LA", + "MD", + "MA", + "MN", + "MS", + "MO", + "NV", + "NY", + "OH", + "OK", + "PA", + "SC", + "SD", + "TN", + "TX", + "VA", + "WV", + "WI", + "WY" + ], + "candidate": [ + "CA", + "MD", + "NV" + ] + }, + "annual_potential_modeled_benefits": { + "baseline": 2253392072.9613476, + "candidate": 5286396512.037729 + } + } +} diff --git a/experiments/us-childcare-attendance/qualified-preparation.json b/experiments/us-childcare-attendance/qualified-preparation.json new file mode 100644 index 000000000..57afb020c --- /dev/null +++ b/experiments/us-childcare-attendance/qualified-preparation.json @@ -0,0 +1,1476 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "source_child_count": 11745, + "source_attrition": [ + { + "status": "age_out_of_scope", + "n": 134, + "child_weight": 633390.6468148227 + }, + { + "status": "ambiguous_calendar", + "n": 882, + "child_weight": 3918044.035953992 + }, + { + "status": "complete", + "n": 7460, + "child_weight": 32249785.682044942 + }, + { + "status": "missing_calendar", + "n": 3095, + "child_weight": 14061828.648228496 + }, + { + "status": "partial_calendar", + "n": 174, + "child_weight": 847769.9869632281 + } + ], + "under13_complete_weight_share": 0.6313901604255374, + "seed": 915, + "evaluation_design": "fixed household diagnostic split; not final population certification", + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "training_children": 5988, + "heldout_children": 1472, + "unsupported_heldout_children": 30, + "unsupported_heldout_weight": 153817.788204796, + "household_overlap": 0, + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1442, + "observed": { + "participation": 0.47956011362829676, + "days_per_week": 1.94220990502671, + "hours_per_week": 14.670259928481958 + }, + "predicted": { + "participation": 0.4547372440086608, + "days_per_week": 1.7986704844660344, + "hours_per_week": 13.001295500494344 + } + }, + { + "grouping": "age", + "group": "0", + "n": 87, + "observed": { + "participation": 0.4749769477860246, + "days_per_week": 1.8705229048944185, + "hours_per_week": 15.234832555457738 + }, + "predicted": { + "participation": 0.21347668238234924, + "days_per_week": 0.7762397588546358, + "hours_per_week": 6.331944692101247 + } + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed": { + "participation": 0.6026124718926947, + "days_per_week": 2.2004884644301415, + "hours_per_week": 16.058889950232714 + }, + "predicted": { + "participation": 0.5817111215090339, + "days_per_week": 2.3867359768549528, + "hours_per_week": 20.29503642359935 + } + }, + { + "grouping": "age", + "group": "2", + "n": 107, + "observed": { + "participation": 0.6827728698846081, + "days_per_week": 3.2446932739752876, + "hours_per_week": 32.643365116161064 + }, + "predicted": { + "participation": 0.721108922787169, + "days_per_week": 2.9279814704204314, + "hours_per_week": 25.4484971084278 + } + }, + { + "grouping": "age", + "group": "3", + "n": 105, + "observed": { + "participation": 0.5478113606887365, + "days_per_week": 2.4737318877881727, + "hours_per_week": 18.774571500322175 + }, + "predicted": { + "participation": 0.7525857598038783, + "days_per_week": 3.556893850206531, + "hours_per_week": 31.34897414405891 + } + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed": { + "participation": 0.6853382748904278, + "days_per_week": 3.133535876664359, + "hours_per_week": 22.72675588135229 + }, + "predicted": { + "participation": 0.6264622224091229, + "days_per_week": 2.555534029105278, + "hours_per_week": 22.558086951188297 + } + }, + { + "grouping": "age", + "group": "5", + "n": 109, + "observed": { + "participation": 0.45965467004039157, + "days_per_week": 2.204288116474054, + "hours_per_week": 16.671100530443887 + }, + "predicted": { + "participation": 0.5197893305690322, + "days_per_week": 2.0628456061601703, + "hours_per_week": 14.660711473842793 + } + }, + { + "grouping": "age", + "group": "6", + "n": 121, + "observed": { + "participation": 0.37767600901948256, + "days_per_week": 1.4233675361325482, + "hours_per_week": 11.368361596236443 + }, + "predicted": { + "participation": 0.3812603191662228, + "days_per_week": 1.344172188177959, + "hours_per_week": 6.687190574415363 + } + }, + { + "grouping": "age", + "group": "7", + "n": 121, + "observed": { + "participation": 0.2903318905157446, + "days_per_week": 0.9137564001326794, + "hours_per_week": 4.425088819370026 + }, + "predicted": { + "participation": 0.3316072853951072, + "days_per_week": 1.3098426672238894, + "hours_per_week": 7.381236186215511 + } + }, + { + "grouping": "age", + "group": "8", + "n": 136, + "observed": { + "participation": 0.33161676756925296, + "days_per_week": 1.2251387657453359, + "hours_per_week": 6.71788070980216 + }, + "predicted": { + "participation": 0.35465278512495324, + "days_per_week": 1.3642638058954168, + "hours_per_week": 9.725344329820171 + } + }, + { + "grouping": "age", + "group": "9", + "n": 101, + "observed": { + "participation": 0.4569510479920196, + "days_per_week": 1.674346958462562, + "hours_per_week": 7.805739183708693 + }, + "predicted": { + "participation": 0.396876880067868, + "days_per_week": 1.3966673442424729, + "hours_per_week": 8.701466574792914 + } + }, + { + "grouping": "age", + "group": "10", + "n": 105, + "observed": { + "participation": 0.45503949077471245, + "days_per_week": 1.5782906105470733, + "hours_per_week": 12.426848636692846 + }, + "predicted": { + "participation": 0.17752112044794535, + "days_per_week": 0.6469841431239514, + "hours_per_week": 3.596759032644189 + } + }, + { + "grouping": "age", + "group": "11", + "n": 119, + "observed": { + "participation": 0.41662598750149954, + "days_per_week": 1.8643111020323588, + "hours_per_week": 12.941021091087745 + }, + "predicted": { + "participation": 0.4008574857176407, + "days_per_week": 1.339718142783517, + "hours_per_week": 7.024111200026524 + } + }, + { + "grouping": "age", + "group": "12", + "n": 103, + "observed": { + "participation": 0.46408861423609865, + "days_per_week": 1.4945247924023801, + "hours_per_week": 11.114322042194766 + }, + "predicted": { + "participation": 0.43591150819042584, + "days_per_week": 1.7008936429461807, + "hours_per_week": 7.10555160431239 + } + }, + { + "grouping": "region", + "group": "1", + "n": 187, + "observed": { + "participation": 0.5340539220646277, + "days_per_week": 2.4009930157522064, + "hours_per_week": 14.79000641069395 + }, + "predicted": { + "participation": 0.6359603067185889, + "days_per_week": 2.7160385976574153, + "hours_per_week": 21.24487930853725 + } + }, + { + "grouping": "region", + "group": "2", + "n": 335, + "observed": { + "participation": 0.46285701128880247, + "days_per_week": 1.8453473655274784, + "hours_per_week": 15.352184287431765 + }, + "predicted": { + "participation": 0.41283532735257467, + "days_per_week": 1.4868890394545802, + "hours_per_week": 9.329182591248077 + } + }, + { + "grouping": "region", + "group": "3", + "n": 470, + "observed": { + "participation": 0.4328632075166446, + "days_per_week": 1.8236781494293404, + "hours_per_week": 15.35364160426729 + }, + "predicted": { + "participation": 0.4184231306502971, + "days_per_week": 1.5947483523709618, + "hours_per_week": 10.342311458512322 + } + }, + { + "grouping": "region", + "group": "4", + "n": 450, + "observed": { + "participation": 0.5171827642556369, + "days_per_week": 1.8475690827448323, + "hours_per_week": 13.04393396272379 + }, + "predicted": { + "participation": 0.4063739455351018, + "days_per_week": 1.670368549893466, + "hours_per_week": 13.748230642009998 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 13, + "observed": { + "participation": 0.253295875991658, + "days_per_week": 0.8671327195533685, + "hours_per_week": 3.515415042668932 + }, + "predicted": { + "participation": 0.12949135028799522, + "days_per_week": 0.5134069370080796, + "hours_per_week": 3.2849650228565768 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 150, + "observed": { + "participation": 0.2939873489805854, + "days_per_week": 1.1857568669020946, + "hours_per_week": 10.001769026104405 + }, + "predicted": { + "participation": 0.20052896295212805, + "days_per_week": 0.7861821143968067, + "hours_per_week": 6.547539835013359 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 543, + "observed": { + "participation": 0.2512596429034898, + "days_per_week": 0.8664048151368835, + "hours_per_week": 5.612021341518408 + }, + "predicted": { + "participation": 0.30234830539829555, + "days_per_week": 0.9663278582430083, + "hours_per_week": 5.557806485223704 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 736, + "observed": { + "participation": 0.6462583482271946, + "days_per_week": 2.714084969528804, + "hours_per_week": 21.02161873348801 + }, + "predicted": { + "participation": 0.5856324643226721, + "days_per_week": 2.4572831629804637, + "hours_per_week": 18.54158986890966 + } + } + ], + "production_ready": false, + "limitations": [ + "Only complete, unambiguous calendars enter this holdout; source selection remains unvalidated.", + "The diagnostic matches age and caller-specified covariates; it is not a nationally validated fitted model.", + "Sibling assignments, mixed providers, older-child care and scalar monthly conversion need validation.", + "No full Microcosm population build or state CCDF distribution is certified by this source-only report." + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "selection_and_cross_validation": { + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "seed": 271828, + "design": "five-fold household-separated conditional-expectation diagnostic", + "coverage": [ + { + "grouping": "questionnaire_version", + "group": "1", + "n": 8516, + "weight": 37015599.704962164, + "usable_weight_share": 0.8712484989868113 + }, + { + "grouping": "questionnaire_version", + "group": "2", + "n": 1716, + "weight": 7945306.22844611, + "usable_weight_share": 0.0 + }, + { + "grouping": "questionnaire_version", + "group": "3", + "n": 1379, + "weight": 6116522.419782387, + "usable_weight_share": 0.0 + }, + { + "grouping": "age", + "group": "0", + "n": 709, + "weight": 3159547.2696341733, + "usable_weight_share": 0.6556513621184303 + }, + { + "grouping": "age", + "group": "1", + "n": 856, + "weight": 3710311.739747545, + "usable_weight_share": 0.6518631977426506 + }, + { + "grouping": "age", + "group": "2", + "n": 906, + "weight": 4112848.861546397, + "usable_weight_share": 0.6567885484115844 + }, + { + "grouping": "age", + "group": "3", + "n": 927, + "weight": 4191268.180093972, + "usable_weight_share": 0.5836151261043054 + }, + { + "grouping": "age", + "group": "4", + "n": 890, + "weight": 3761621.476875998, + "usable_weight_share": 0.6069150244376095 + }, + { + "grouping": "age", + "group": "5", + "n": 884, + "weight": 3759953.5032736305, + "usable_weight_share": 0.6441250249176611 + }, + { + "grouping": "age", + "group": "6", + "n": 954, + "weight": 4259102.717846392, + "usable_weight_share": 0.6213110691400029 + }, + { + "grouping": "age", + "group": "7", + "n": 944, + "weight": 4191490.05150999, + "usable_weight_share": 0.6014263712726516 + }, + { + "grouping": "age", + "group": "8", + "n": 925, + "weight": 3969583.419307067, + "usable_weight_share": 0.6776062606911202 + }, + { + "grouping": "age", + "group": "9", + "n": 921, + "weight": 4036325.188020362, + "usable_weight_share": 0.6661696977311178 + }, + { + "grouping": "age", + "group": "10", + "n": 884, + "weight": 3707790.894951107, + "usable_weight_share": 0.6220814618665044 + }, + { + "grouping": "age", + "group": "11", + "n": 908, + "weight": 4069233.422108399, + "usable_weight_share": 0.6084497314858798 + }, + { + "grouping": "age", + "group": "12", + "n": 903, + "weight": 4148351.6282756263, + "usable_weight_share": 0.6217216150291205 + }, + { + "grouping": "region", + "group": "1", + "n": 1468, + "weight": 8153076.12536276, + "usable_weight_share": 0.6401722331669089 + }, + { + "grouping": "region", + "group": "2", + "n": 2680, + "weight": 10714972.208999828, + "usable_weight_share": 0.6330942004076335 + }, + { + "grouping": "region", + "group": "3", + "n": 4275, + "weight": 20022339.81304313, + "usable_weight_share": 0.6249460927360144 + }, + { + "grouping": "region", + "group": "4", + "n": 3188, + "weight": 12187040.205784943, + "usable_weight_share": 0.634603874645437 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 188, + "weight": 819575.738509818, + "usable_weight_share": 0.646736557730207 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1470, + "weight": 5301911.400981113, + "usable_weight_share": 0.6698890755679009 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 3380, + "weight": 14009836.058523985, + "usable_weight_share": 0.7212981495115555 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 6573, + "weight": 30946105.155175745, + "usable_weight_share": 0.5836849144158337 + }, + { + "grouping": "income_band", + "group": "0", + "n": 120, + "weight": 497291.815717528, + "usable_weight_share": 0.602001589054288 + }, + { + "grouping": "income_band", + "group": "1", + "n": 2057, + "weight": 7466809.932490364, + "usable_weight_share": 0.6413274998297077 + }, + { + "grouping": "income_band", + "group": "2", + "n": 2904, + "weight": 10880663.16706181, + "usable_weight_share": 0.6487048847817203 + }, + { + "grouping": "income_band", + "group": "3", + "n": 2558, + "weight": 11325516.809686286, + "usable_weight_share": 0.6401496463974632 + }, + { + "grouping": "income_band", + "group": "4", + "n": 3134, + "weight": 15757011.988844048, + "usable_weight_share": 0.6309648019074636 + }, + { + "grouping": "income_band", + "group": "5", + "n": 838, + "weight": 5150134.639390623, + "usable_weight_share": 0.5652783862122169 + } + ], + "cross_validation": [ + { + "grouping": "all", + "group": "all", + "n": 7460, + "observed": { + "participation": 0.46514845107061753, + "days": 1.8750709563867467, + "hours": 14.240422572045741 + }, + "expected": { + "participation": 0.4654844290416027, + "days": 1.878527294011518, + "hours": 14.081114700859393 + } + }, + { + "grouping": "age", + "group": "0", + "n": 458, + "observed": { + "participation": 0.40007662898763713, + "days": 1.5207410031354076, + "hours": 12.391341719561105 + }, + "expected": { + "participation": 0.42043937266485903, + "days": 1.5581186938006875, + "hours": 12.778280226573155 + } + }, + { + "grouping": "age", + "group": "1", + "n": 585, + "observed": { + "participation": 0.5768442364096245, + "days": 2.3247221464109487, + "hours": 18.93194004904792 + }, + "expected": { + "participation": 0.5636534387398192, + "days": 2.2787632897750942, + "hours": 18.46136873433321 + } + }, + { + "grouping": "age", + "group": "2", + "n": 599, + "observed": { + "participation": 0.6119472630516792, + "days": 2.661365110302835, + "hours": 23.568671427585098 + }, + "expected": { + "participation": 0.615171451571855, + "days": 2.6668583554129937, + "hours": 24.008204825875836 + } + }, + { + "grouping": "age", + "group": "3", + "n": 597, + "observed": { + "participation": 0.6106570906701301, + "days": 2.6641302585227766, + "hours": 21.96346327818111 + }, + "expected": { + "participation": 0.6452189769582379, + "days": 2.8385499518769803, + "hours": 23.234965023661523 + } + }, + { + "grouping": "age", + "group": "4", + "n": 550, + "observed": { + "participation": 0.7046928755645677, + "days": 3.1467359328380096, + "hours": 25.30894736806148 + }, + "expected": { + "participation": 0.7168364294128767, + "days": 3.1704359925035326, + "hours": 25.23962440956216 + } + }, + { + "grouping": "age", + "group": "5", + "n": 581, + "observed": { + "participation": 0.5580840489623686, + "days": 2.527957605747102, + "hours": 20.351246817207983 + }, + "expected": { + "participation": 0.5580783794454302, + "days": 2.4817744855985993, + "hours": 19.696005730111843 + } + }, + { + "grouping": "age", + "group": "6", + "n": 611, + "observed": { + "participation": 0.38410943637037254, + "days": 1.5233703763582376, + "hours": 9.272995362839891 + }, + "expected": { + "participation": 0.3704517476571586, + "days": 1.4435320487985335, + "hours": 8.308626409800924 + } + }, + { + "grouping": "age", + "group": "7", + "n": 586, + "observed": { + "participation": 0.34047335603475937, + "days": 1.267585660002729, + "hours": 8.978033563109225 + }, + "expected": { + "participation": 0.32048663598972876, + "days": 1.2092330340598734, + "hours": 7.976819608666644 + } + }, + { + "grouping": "age", + "group": "8", + "n": 611, + "observed": { + "participation": 0.37000227568041516, + "days": 1.4234519706143929, + "hours": 8.822905923973602 + }, + "expected": { + "participation": 0.37441818500322505, + "days": 1.4678570808250095, + "hours": 8.581931573672641 + } + }, + { + "grouping": "age", + "group": "9", + "n": 590, + "observed": { + "participation": 0.3862968300759907, + "days": 1.373131779820256, + "hours": 9.825473717875598 + }, + "expected": { + "participation": 0.3658218612710925, + "days": 1.2745107095149821, + "hours": 9.212380152023707 + } + }, + { + "grouping": "age", + "group": "10", + "n": 541, + "observed": { + "participation": 0.39185689727904155, + "days": 1.3647219850376735, + "hours": 8.468134791861964 + }, + "expected": { + "participation": 0.3879904743599543, + "days": 1.313066433397897, + "hours": 7.6091158562940935 + } + }, + { + "grouping": "age", + "group": "11", + "n": 565, + "observed": { + "participation": 0.3768238596848206, + "days": 1.3633597325953704, + "hours": 8.143708694787577 + }, + "expected": { + "participation": 0.37027421084947587, + "days": 1.3896706621501198, + "hours": 8.180743029734462 + } + }, + { + "grouping": "age", + "group": "12", + "n": 586, + "observed": { + "participation": 0.35838594666001994, + "days": 1.3239431239565607, + "hours": 10.203528792841063 + }, + "expected": { + "participation": 0.37173621674013835, + "days": 1.447846572682685, + "hours": 10.94364602719194 + } + }, + { + "grouping": "region", + "group": "1", + "n": 929, + "observed": { + "participation": 0.4988625902951673, + "days": 2.101321934458425, + "hours": 14.697745432365767 + }, + "expected": { + "participation": 0.5341920187102704, + "days": 2.234373766910041, + "hours": 15.515545077772881 + } + }, + { + "grouping": "region", + "group": "2", + "n": 1654, + "observed": { + "participation": 0.495642471526947, + "days": 1.9378635600824832, + "hours": 14.438707404977198 + }, + "expected": { + "participation": 0.48657470779470846, + "days": 1.9053160452954483, + "hours": 13.830397580793818 + } + }, + { + "grouping": "region", + "group": "3", + "n": 2752, + "observed": { + "participation": 0.4310162776090592, + "days": 1.7482660126296112, + "hours": 14.16760866653331 + }, + "expected": { + "participation": 0.4254047590539008, + "days": 1.728432288982956, + "hours": 13.771990873343658 + } + }, + { + "grouping": "region", + "group": "4", + "n": 2125, + "observed": { + "participation": 0.470872102608504, + "days": 1.872465315119329, + "hours": 13.875678601794997 + }, + "expected": { + "participation": 0.46546295898430057, + "days": 1.857723153971244, + "hours": 13.833112421687238 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 122, + "observed": { + "participation": 0.34614706740243706, + "days": 1.1849281571656916, + "hours": 9.788566562075097 + }, + "expected": { + "participation": 0.373389275855184, + "days": 1.462386368172592, + "hours": 12.574282386813668 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1016, + "observed": { + "participation": 0.3096578455166947, + "days": 1.244408501915661, + "hours": 10.193456359940383 + }, + "expected": { + "participation": 0.2882996076692788, + "days": 1.1762845771341546, + "hours": 10.282450861819337 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 2418, + "observed": { + "participation": 0.2864480974767621, + "days": 0.9529380212414282, + "hours": 6.42952602558992 + }, + "expected": { + "participation": 0.29059369126489437, + "days": 0.9858655333196232, + "hours": 6.764091600272923 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 3904, + "observed": { + "participation": 0.5991891288528669, + "days": 2.535220374688929, + "hours": 19.536646150926547 + }, + "expected": { + "participation": 0.6008699918772364, + "days": 2.528223299633135, + "hours": 18.96579429859326 + } + }, + { + "grouping": "income_band", + "group": "0", + "n": 76, + "observed": { + "participation": 0.19070472825408744, + "days": 0.8431547354115522, + "hours": 5.587881885800679 + }, + "expected": { + "participation": 0.25300464764173586, + "days": 0.9988467887623982, + "hours": 7.43593331362669 + } + }, + { + "grouping": "income_band", + "group": "1", + "n": 1378, + "observed": { + "participation": 0.35684525807866907, + "days": 1.5280931021110342, + "hours": 12.098088591903252 + }, + "expected": { + "participation": 0.3773650597658377, + "days": 1.627338456412184, + "hours": 12.706694579809993 + } + }, + { + "grouping": "income_band", + "group": "2", + "n": 1922, + "observed": { + "participation": 0.4194011939997811, + "days": 1.7068232976926216, + "hours": 13.491053608546364 + }, + "expected": { + "participation": 0.4119472083085292, + "days": 1.7030609232530567, + "hours": 13.262000601507708 + } + }, + { + "grouping": "income_band", + "group": "3", + "n": 1632, + "observed": { + "participation": 0.4246978564102466, + "days": 1.7238735833385048, + "hours": 14.076591113511121 + }, + "expected": { + "participation": 0.4255788471352766, + "days": 1.704797441331804, + "hours": 13.264862389978802 + } + }, + { + "grouping": "income_band", + "group": "4", + "n": 1977, + "observed": { + "participation": 0.5221816116808028, + "days": 1.9968956951019248, + "hours": 14.35832226780555 + }, + "expected": { + "participation": 0.519705526319959, + "days": 1.9988673657729517, + "hours": 14.55106672367201 + } + }, + { + "grouping": "income_band", + "group": "5", + "n": 475, + "observed": { + "participation": 0.6883939484468758, + "days": 2.9203317046618578, + "hours": 20.476264713601847 + }, + "expected": { + "participation": 0.6762909354194406, + "days": 2.8292574855634296, + "hours": 19.43897806431697 + } + } + ], + "matching_levels": { + "age,region,parent_work_status,income_band": 7278, + "age,parent_work_status,income_band": 150, + "age,parent_work_status": 32 + }, + "sibling_validation": [ + { + "fold": 0, + "training_rho": 0.7504775690168417, + "households": 351, + "weight": 1627200.3809817089, + "observed_both": 0.3346110552302055, + "independent_both": 0.26023741618799584, + "coupled_both": 0.3228394147816889 + }, + { + "fold": 1, + "training_rho": 0.8095401305423818, + "households": 367, + "weight": 1640369.17044373, + "observed_both": 0.308478237709926, + "independent_both": 0.2538314963354507, + "coupled_both": 0.31777192913378116 + }, + { + "fold": 2, + "training_rho": 0.7176223341350483, + "households": 443, + "weight": 1849383.6729044288, + "observed_both": 0.3831182075641927, + "independent_both": 0.260592140353083, + "coupled_both": 0.3144997720634755 + }, + { + "fold": 3, + "training_rho": 0.8946850037821993, + "households": 398, + "weight": 1567825.4608580691, + "observed_both": 0.2954259187077949, + "independent_both": 0.2551329149477867, + "coupled_both": 0.3186981148017382 + }, + { + "fold": 4, + "training_rho": 0.8022505236076816, + "households": 382, + "weight": 1644777.832273697, + "observed_both": 0.3129856591731878, + "independent_both": 0.2695651435301845, + "coupled_both": 0.3411406780146054 + } + ], + "questionnaire_transport": [ + { + "questionnaire_version": 2, + "scored_children": 1690, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.4166998042800092, + "regular_hours_per_week": 12.379208437570716 + }, + "expected": { + "regular_participation": 0.429264128691119, + "regular_hours_per_week": 14.800674181069196 + } + }, + { + "questionnaire_version": 3, + "scored_children": 1356, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.38447915314763226, + "regular_hours_per_week": 10.163827277430636 + }, + "expected": { + "regular_participation": 0.40786334693307874, + "regular_hours_per_week": 13.921490221932158 + } + } + ], + "assumptions": [ + "Calendar selection is ignorable conditional on matching fields; not identified from these data.", + "Main reference-week schedules transfer to May/fall typical weeks; summer attendance is not observed.", + "Regular-care comparisons cannot validate days or irregular care in instruments without calendars." + ], + "production_ready": false + }, + "masked_calendar_validation": { + "seed": 161803, + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1581, + "observed_days": 1.7176113066572272, + "imputed_days": 1.7931671544992573, + "observed_hours": 13.097316141278531, + "imputed_hours": 13.161738723028055 + }, + { + "grouping": "age", + "group": "0", + "n": 104, + "observed_days": 1.841126169101498, + "imputed_days": 1.757972840808916, + "observed_hours": 17.766331967357488, + "imputed_hours": 18.04127357697798 + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed_days": 2.37969546844872, + "imputed_days": 2.466724275790995, + "observed_hours": 19.829328945193588, + "imputed_hours": 19.122886717221327 + }, + { + "grouping": "age", + "group": "2", + "n": 141, + "observed_days": 2.4591773765558673, + "imputed_days": 2.5150278262191854, + "observed_hours": 22.536051090399965, + "imputed_hours": 22.20070627284854 + }, + { + "grouping": "age", + "group": "3", + "n": 123, + "observed_days": 2.3763627081083216, + "imputed_days": 2.276649667113583, + "observed_hours": 20.251977914853146, + "imputed_hours": 19.002839959330025 + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed_days": 3.137397964036117, + "imputed_days": 3.2507274717120613, + "observed_hours": 23.68360614421261, + "imputed_hours": 24.493916641155817 + }, + { + "grouping": "age", + "group": "5", + "n": 113, + "observed_days": 2.2242008794879244, + "imputed_days": 2.6293843819994196, + "observed_hours": 15.760856040214335, + "imputed_hours": 17.46780844312604 + }, + { + "grouping": "age", + "group": "6", + "n": 130, + "observed_days": 1.1830651060264654, + "imputed_days": 1.4124105587435267, + "observed_hours": 7.042175416036001, + "imputed_hours": 6.515374725210956 + }, + { + "grouping": "age", + "group": "7", + "n": 131, + "observed_days": 1.4073709771017413, + "imputed_days": 1.2967131682238184, + "observed_hours": 10.083939201157254, + "imputed_hours": 9.618996783594744 + }, + { + "grouping": "age", + "group": "8", + "n": 126, + "observed_days": 1.057579033652681, + "imputed_days": 1.0820232296805903, + "observed_hours": 8.256788366496435, + "imputed_hours": 6.9299612873656375 + }, + { + "grouping": "age", + "group": "9", + "n": 118, + "observed_days": 1.4349301372689758, + "imputed_days": 1.2570443106530333, + "observed_hours": 10.733568940718998, + "imputed_hours": 11.528485757775337 + }, + { + "grouping": "age", + "group": "10", + "n": 124, + "observed_days": 1.2058459898990705, + "imputed_days": 1.2067301677274491, + "observed_hours": 4.1774445652404655, + "imputed_hours": 4.342805855105561 + }, + { + "grouping": "age", + "group": "11", + "n": 120, + "observed_days": 0.9483350107584563, + "imputed_days": 1.2273618861530076, + "observed_hours": 5.566349482238208, + "imputed_hours": 6.3416466466885835 + }, + { + "grouping": "age", + "group": "12", + "n": 123, + "observed_days": 1.1181905452629102, + "imputed_days": 1.3377626847259545, + "observed_hours": 8.079018679724042, + "imputed_hours": 9.278796503817835 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 33, + "observed_days": 1.5430497710033617, + "imputed_days": 1.3274872915746017, + "observed_hours": 12.754941207431992, + "imputed_hours": 12.147679308677683 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 233, + "observed_days": 1.3163214930511598, + "imputed_days": 1.4661772450887058, + "observed_hours": 9.57367968242547, + "imputed_hours": 9.490248507911081 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 529, + "observed_days": 0.8935323002915262, + "imputed_days": 1.0535312696492358, + "observed_hours": 6.370457810059637, + "imputed_hours": 7.0815725747740155 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 786, + "observed_days": 2.3152602893352414, + "imputed_days": 2.332856367184494, + "observed_hours": 17.994069548324816, + "imputed_hours": 17.722049473088507 + } + ], + "regular_hours_preserved": true, + "limitations": "Tests reconstruction where calendars exist; not a direct test of unobserved days in the other questionnaire instruments." + }, + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "candidate_frame_written": true, + "environment": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "microcosm-build": "0.1.0", + "microcosm-frame": "0.1.0", + "policyengine-us": "1.819.0", + "python": "3.13.5" + }, + "code_sha256": { + "prepare_us_childcare_attendance.py": "94cc41dd804508dbd0d1340ee1ac1b0189dfbcd8f089fc403a9933552e50d799", + "childcare_attendance.py": "43d6ed235cc52ec6d9b6fcde15d10f66e8bb870b8e3cf5232b35573917a1e041", + "nsece_childcare.py": "ecd99fdf630f51c5f75df141ff8a054a5124288cda6a2dbdb338ceb5abae280f", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "bf9ff3a1992d16c75fc74b8c17634791739669f0ecab4382fdf59a6e6c3c424c", + "childcare_attendance_stage.py": "fced2bd83564d5d6332d4a5511051ca041acd0e1b6983acfc3035fe31ea5b6de", + "nsece_childcare_assessment.py": "537d5d839ce9df4f1772ee887597811990aab5b7f10a863b70fe6630583fa3b7", + "childcare_population.py": "f34711db1f692347a13070a4c6faa7612ebf7c97cba156ca488781559516b6be", + "childcare.py": "a8502e1fc15457a7f120936a8940fbb51cba3e2299d92ca9cd69b749f6deea2f", + "childcare_attendance_source.json": "c5beeeb09ab330c305e3250bc7db5a5003336d26fa508872211cb18958ab9e97", + "education_assistance_source.py": "e259e3fce64fdcab62953f79a1aa41719f81c64c7c5dc1a63f8b1e493ff5f83a", + "geography_constants.py": "dc31ec6aa913d638d51ccc8e3fda11edee6c3f508ea5d2e27690bdb43e081fd2", + "uv.lock": "751d5ef5d25406bbae1798667f0e29890d4aad933d323c12912c7d45d8809bb9" + }, + "candidate_checkpoint_sha256": "882c2ae6b683e2a04ce6c94f5f694f70750a8c2876e8108e3715a7f7095d1f66", + "parent_population_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "production_stage_executed": true, + "candidate_receipts": { + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_outside_domain_baseline": { + "engine_version": "1.819.0", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operation_order": [ + "derive_childcare_inputs", + "derive_childcare_inputs", + "derive_childcare_inputs" + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + } + }, + "native_candidate_written": true, + "native_candidate_sha256": "a2d0ed98081b3707727eddb3d90e007117aa1da50e75c83a1a33fe381f41bcd2" +} diff --git a/experiments/us-childcare-attendance/qualified-remaining-state-inputs.json b/experiments/us-childcare-attendance/qualified-remaining-state-inputs.json new file mode 100644 index 000000000..f4bbe77ea --- /dev/null +++ b/experiments/us-childcare-attendance/qualified-remaining-state-inputs.json @@ -0,0 +1,156 @@ +{ + "candidate_checkpoint_sha256": "882c2ae6b683e2a04ce6c94f5f694f70750a8c2876e8108e3715a7f7095d1f66", + "engine_version": "1.819.0", + "scope": "January 2026 diagnostic; fixed parent ages and income", + "states": { + "CA": { + "ca_capp_eligible": { + "period": "2026-01", + "units": 2828, + "positive_units": 341, + "unique_counts": { + "False": 2487, + "True": 341 + } + }, + "ca_calworks_child_care_days_per_month": { + "period": "2026-01", + "units": 7866, + "positive_units": 0, + "unique_counts": { + "0": 7866 + } + }, + "ca_calworks_child_care_weeks_per_month": { + "period": "2026-01", + "units": 7866, + "positive_units": 0, + "unique_counts": { + "0": 7866 + } + }, + "ca_calworks_child_care_time_coefficient": { + "period": "2026-01", + "units": 7866, + "positive_units": 0, + "unique_counts": { + "0.0": 7866 + } + }, + "ca_calworks_child_care_payment_standard": { + "period": "2026-01", + "units": 7866, + "positive_units": 1531, + "unique_counts": { + "0.0": 6335, + "139.94": 886, + "227.58": 207, + "277.15": 83, + "277.58": 111, + "309.93": 188, + "406.72": 56 + } + }, + "ca_capp_payment": { + "period": "2026-01", + "units": 7866, + "positive_units": 0, + "unique_counts": { + "0.0": 7866 + } + }, + "pre_subsidy_childcare_expenses": { + "period": "2026", + "units": 7866, + "positive_units": 313, + "max": 90000.0 + } + }, + "MD": { + "md_ccs_eligible": { + "period": "2026-01", + "units": 1193, + "positive_units": 115, + "unique_counts": { + "False": 1078, + "True": 115 + } + }, + "md_ccs_provider_type": { + "period": "2026-01", + "units": 3268, + "positive_units": null, + "unique_counts": { + "NONE": 3268 + } + }, + "md_ccs_payment_rate": { + "period": "2026-01", + "units": 3268, + "positive_units": 0, + "unique_counts": { + "0.0": 3268 + } + }, + "md_ccs": { + "period": "2026-01", + "units": 1193, + "positive_units": 0, + "unique_counts": { + "0.0": 1193 + } + } + }, + "NV": { + "meets_ccdf_activity_test": { + "period": "2026", + "units": 1011, + "positive_units": 0, + "unique_counts": { + "False": 1011 + } + }, + "nv_ccdp_activity_eligible": { + "period": "2026-01", + "units": 1011, + "positive_units": 0, + "unique_counts": { + "False": 1011 + } + }, + "nv_ccdp_income_eligible": { + "period": "2026-01", + "units": 1011, + "positive_units": 149, + "unique_counts": { + "False": 862, + "True": 149 + } + }, + "is_ccdf_asset_eligible": { + "period": "2026", + "units": 1011, + "positive_units": 1011, + "unique_counts": { + "True": 1011 + } + }, + "nv_ccdp_eligible": { + "period": "2026-01", + "units": 1011, + "positive_units": 0, + "unique_counts": { + "False": 1011 + } + }, + "nv_ccdp": { + "period": "2026-01", + "units": 1011, + "positive_units": 0, + "unique_counts": { + "0.0": 1011 + } + } + } + } +} diff --git a/packages/microcosm-build/src/microcosm/build/us/childcare_attendance_source.json b/packages/microcosm-build/src/microcosm/build/us/childcare_attendance_source.json new file mode 100644 index 000000000..26dd567f1 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us/childcare_attendance_source.json @@ -0,0 +1,22 @@ +{ + "stage": "nsece_childcare_attendance", + "survey": "2024 NSECE household DS5 and calendar DS4, ICPSR39466.v1", + "source": "https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation", + "grain": "person", + "artifacts": [ + {"dataset": "DS5", "format": "tsv", "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2"}, + {"dataset": "DS4", "format": "tsv", "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e"} + ], + "operations": [ + {"kind": "derive_childcare_inputs", "operation": "calendar_attendance", "age_min": 0, "age_max": 12}, + {"kind": "derive_childcare_inputs", "operation": "regular_hours_schedule_bridge", "nearest_donors": 10}, + {"kind": "derive_childcare_inputs", "operation": "joint_weighted_schedule_transfer", "preserve_observed": true} + ], + "outputs": ["childcare_attending_days_per_month", "childcare_days_per_week", "childcare_hours_per_day"], + "nonnegative_outputs": ["childcare_attending_days_per_month", "childcare_days_per_week", "childcare_hours_per_day"], + "income_reference_year": 2023, + "income_band_upper_bounds": [0, 25000, 50000, 100000, 200000], + "cpi_u_annual_average": {"2022": 292.655, "2023": 304.702, "2024": 313.689}, + "cpi_source": "https://www.bls.gov/cpi/tables/supplemental-files/historical-cpi-u-202412.pdf", + "notes": "Local licensed-source build extension. Scope is ages 0-12. Unresolved records outside the source domain are not observations of zero care. Calendar and bridge provenance must accompany population outputs. Publication is separate from source preparation." +} diff --git a/packages/microcosm-build/src/microcosm/build/us/country_package.json b/packages/microcosm-build/src/microcosm/build/us/country_package.json index 34e30f6dc..71e9421d7 100644 --- a/packages/microcosm-build/src/microcosm/build/us/country_package.json +++ b/packages/microcosm-build/src/microcosm/build/us/country_package.json @@ -161,6 +161,11 @@ "path": "tax_expenditure_reforms.json", "kind": "legacy_json", "schema_id": "legacy_json" + }, + { + "path": "childcare_attendance_source.json", + "kind": "legacy_json", + "schema_id": "legacy_json" } ] } diff --git a/packages/microcosm-build/src/microcosm/build/us/release_input_coverage_manifest.json b/packages/microcosm-build/src/microcosm/build/us/release_input_coverage_manifest.json index b352ee60e..06f8ebe3c 100644 --- a/packages/microcosm-build/src/microcosm/build/us/release_input_coverage_manifest.json +++ b/packages/microcosm-build/src/microcosm/build/us/release_input_coverage_manifest.json @@ -47,6 +47,18 @@ "child_support_received": { "status": "required" }, + "childcare_attending_days_per_month": { + "note": "Person-level NSECE attendance source extension (#915): requires persisted signal; missing inputs must not silently become engine defaults. The modeled age domain and outside-domain baseline policy are recorded in source coverage.", + "status": "required" + }, + "childcare_days_per_week": { + "note": "Person-level NSECE attendance source extension (#915): requires persisted signal; missing inputs must not silently become engine defaults. The modeled age domain and outside-domain baseline policy are recorded in source coverage.", + "status": "required" + }, + "childcare_hours_per_day": { + "note": "Person-level NSECE attendance source extension (#915): requires persisted signal; missing inputs must not silently become engine defaults. The modeled age domain and outside-domain baseline policy are recorded in source coverage.", + "status": "required" + }, "congressional_district_geoid": { "status": "required" }, @@ -534,11 +546,11 @@ } }, "counts": { - "required": 163, + "required": 166, "reviewed_exclusion": 7, - "total": 170 + "total": 173 }, - "derivation": "Required surface = input columns in the pinned, sha-verified ecps_parity_reference.json populated layers, plus the documented post-reference fsla_overtime_premium, qualified_passenger_vehicle_loan_interest, five desired retirement-contribution inputs, meets_ssi_disability_criteria required by shipped validation probes, and the #282 Schedule-D capital-gain-distributions route leg schedule_d_capital_gain_distributions (PolicyEngine/microcosm#462). status='reviewed_exclusion' for ecps_parity_known_gaps.json entries (reason+issue from that register); EXCEPT every primary-source restoration pinned by RESTORED_REFERENCE_ECPS_REQUIRED_INPUTS (including the Section 199A QBI family), and the SSI countable-resource asset inputs (bank_account_assets, stock_assets, bond_assets), which are status='required' with NO exclusion per PolicyEngine/microcosm#368 so the gate fails on today's artifacts and asset restoration (Deliverable 2) turns it green. All other populated layers are 'required'. Regenerate with tools/build_us_release_input_coverage_manifest.py.", + "derivation": "Required surface = input columns in the pinned, sha-verified ecps_parity_reference.json populated layers, plus the documented post-reference fsla_overtime_premium, qualified_passenger_vehicle_loan_interest, five desired retirement-contribution inputs, meets_ssi_disability_criteria required by shipped validation probes, the three NSECE child attendance inputs (#915), and the #282 Schedule-D capital-gain-distributions route leg schedule_d_capital_gain_distributions (PolicyEngine/microcosm#462). status='reviewed_exclusion' for ecps_parity_known_gaps.json entries (reason+issue from that register); EXCEPT every primary-source restoration pinned by RESTORED_REFERENCE_ECPS_REQUIRED_INPUTS (including the Section 199A QBI family), and the SSI countable-resource asset inputs (bank_account_assets, stock_assets, bond_assets), which are status='required' with NO exclusion per PolicyEngine/microcosm#368 so the gate fails on today's artifacts and asset restoration (Deliverable 2) turns it green. All other populated layers are 'required'. Regenerate with tools/build_us_release_input_coverage_manifest.py.", "description": "Declared full-coverage contract for a US release: every input column the reference eCPS exports must be persisted as a key with non-default signal, or carry a reviewed exclusion. Enforced as a hard release gate (microcosm.build.us_runtime.release_input_coverage) that generalizes assert_required_us_release_source_columns from 5 columns to the full eCPS input surface.", "issue": "PolicyEngine/microcosm#368", "reference": { diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py index 91ba39f34..54a59f0d0 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py @@ -9,6 +9,7 @@ import hashlib import json +from importlib.resources import files import numpy as np import pandas as pd @@ -22,6 +23,24 @@ ) +def childcare_attendance_contract() -> dict: + return json.loads( + files("microcosm.build.us") + .joinpath("childcare_attendance_source.json") + .read_text() + ) + + +def childcare_income_band(income): + """Band household income expressed in the contract's reference-year dollars.""" + values = np.asarray(income, dtype=float) + if not np.isfinite(values).all(): + raise ValueError("Childcare income must be observed and finite.") + return np.searchsorted( + childcare_attendance_contract()["income_band_upper_bounds"], values, side="left" + ) + + def _validate_attendance(table: pd.DataFrame, *, complete: bool) -> None: values = table[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].to_numpy( dtype=float, na_value=np.nan @@ -60,6 +79,8 @@ def impute_us_childcare_attendance( donor_weights: Weights, match_columns: tuple[str, ...], seed: int, + fallback_match_columns: tuple[tuple[str, ...], ...] = (), + sibling_dependence: float = 0.0, ) -> pd.DataFrame: """Fill missing attendance for ages 0–12 by a joint weighted donor draw. @@ -87,6 +108,20 @@ def impute_us_childcare_attendance( or set(match_columns) & set(US_CHILDCARE_ATTENDANCE_COLUMNS) ): raise ValueError("Unique matching columns must include age, not attendance.") + if not np.isfinite(sibling_dependence) or not 0 <= sibling_dependence <= 1: + raise ValueError("Sibling dependence must be between zero and one.") + if sibling_dependence > 0: + _ids(person, "childcare_source_household_id", unique=False) + levels = (match_columns, *fallback_match_columns) + if any( + "age" not in level + or len(set(level)) != len(level) + or not set(level).issubset(match_columns) + for level in levels + ): + raise ValueError( + "Childcare fallback fields must be unique subsets retaining age." + ) for table, required in ( (person, ("person_source_id", *match_columns)), (donor, ("donor_id", *match_columns, *US_CHILDCARE_ATTENDANCE_COLUMNS)), @@ -133,8 +168,23 @@ def impute_us_childcare_attendance( _validate_attendance(result, complete=False) _validate_attendance(pool, complete=True) pool["_donor_weight"] = donor_weights.values - pool = pool.sort_values("donor_id").reset_index(drop=True) - groups = pool.groupby(list(match_columns), sort=False, dropna=False).indices + order = ( + [ + US_CHILDCARE_ATTENDANCE_COLUMNS[1], + US_CHILDCARE_ATTENDANCE_COLUMNS[2], + "donor_id", + ] + if sibling_dependence > 0 + else ["donor_id"] + ) + pool = pool.sort_values(order).reset_index(drop=True) + groups = [ + pool.groupby(list(level), sort=False, dropna=False).indices for level in levels + ] + if fallback_match_columns and "childcare_attendance_match_level" not in result: + result["childcare_attendance_match_level"] = pd.Series( + pd.NA, index=result.index, dtype="string" + ) for source_id, rows in result.groupby( "person_source_id", sort=False @@ -162,13 +212,15 @@ def impute_us_childcare_attendance( selected = known source = f"source_person:{source_id}" else: - key = tuple(replicas[column].iloc[0] for column in match_columns) - # pandas uses scalar group keys for one matching column. - group_key = key[0] if len(key) == 1 else key - candidates = pool.iloc[groups.get(group_key, [])] - candidates = candidates.loc[candidates["_donor_weight"] > 0] - for column, value in known.items(): - candidates = candidates.loc[candidates[column] == value] + for level, group in zip(levels, groups, strict=True): + key = tuple(replicas[column].iloc[0] for column in level) + group_key = key[0] if len(key) == 1 else key + candidates = pool.iloc[group.get(group_key, [])] + candidates = candidates.loc[candidates["_donor_weight"] > 0] + for column, value in known.items(): + candidates = candidates.loc[candidates[column] == value] + if not candidates.empty: + break if candidates.empty: raise ValueError( f"No compatible positive-weight childcare donor: {source_id}." @@ -178,6 +230,22 @@ def impute_us_childcare_attendance( weights = weights / weights.max() cumulative = np.cumsum(weights / weights.sum()) payload = json.dumps([int(seed), "childcare_attendance", source_id]) + if sibling_dependence > 0: + household_ids = replicas.childcare_source_household_id.unique() + if len(household_ids) != 1: + raise ValueError( + "Childcare source clones disagree about household identity." + ) + household = household_ids[0] + selection = hashlib.sha256( + json.dumps([int(seed), "sibling_mixture", household]).encode() + ).digest() + if ( + int.from_bytes(selection[:8], "big") >> 11 + ) / 2**53 < sibling_dependence: + payload = json.dumps( + [int(seed), "childcare_household_rank", household] + ) digest = hashlib.sha256(payload.encode()).digest() draw = (int.from_bytes(digest[:8], "big") >> 11) / 2**53 position = min( @@ -186,6 +254,8 @@ def impute_us_childcare_attendance( ) selected = candidates.iloc[position] source = f"donor:{selected['donor_id']}" + if fallback_match_columns: + result.loc[rows, "childcare_attendance_match_level"] = ",".join(level) for column in US_CHILDCARE_ATTENDANCE_COLUMNS: empty_rows = replicas.index[replicas[column].isna()] result.loc[empty_rows, column] = selected[column] diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py new file mode 100644 index 000000000..434135e20 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py @@ -0,0 +1,220 @@ +"""Build-stage orchestration for the licensed NSECE attendance source extension. + +The existing generation-0 source manifest is byte-frozen. This extension has a +separate packaged SourceStageSpec and runs after relationship/hours producers. +It is also callable on an exact native parent for candidate qualification. +""" + +from __future__ import annotations + +import json +import shutil +from importlib.metadata import version +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.serialization_dtypes import canonicalize_table_string_dtypes +from microcosm.build.source_manifest import SourceStageSpec +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + childcare_attendance_contract, +) +from microcosm.build.us_runtime.childcare_population import ( + harmonize_asec_childcare_predictors, +) +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, + NSECE_CHILDCARE_MATCH_COLUMNS, + assert_childcare_attendance_exportable, + load_nsece_childcare, + with_us_nsece_childcare_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_dependence import ( + fit_nsece_sibling_dependence, +) +from microcosm.frame import Frame + + +def inherit_outside_domain_attendance_baseline(frame: Frame) -> Frame: + """Explicit export policy: retain engine baseline only outside modeled ages. + + This does NOT infer measured nonattendance for teens or disabled older + children. Receipts identify the inherited values and source-domain limits. + Observed values are preserved and all under-13 records must be resolved. + """ + from policyengine_us import CountryTaxBenefitSystem + + people = frame.table("person").copy() + system = CountryTaxBenefitSystem() + outside = ~people.age.between(0, 12) + defaults, inherited = {}, {} + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + if column not in people: + raise ValueError( + "Attendance must be imputed before inheriting the outside-domain baseline." + ) + if people.loc[~outside, column].isna().any(): + raise ValueError( + "Cannot inherit baseline for unresolved under-13 attendance." + ) + default = system.variables[column].default_value + if not np.isfinite(default): + raise ValueError("The pinned engine attendance baseline must be finite.") + missing = outside & people[column].isna() + people.loc[missing, column] = default + people.loc[missing, f"{column}_source"] = ( + "inherited_engine_baseline_outside_age_0_12" + ) + defaults[column] = float(default) + inherited[column] = int(missing.sum()) + tables = {entity: frame.table(entity) for entity in frame.entities} + tables["person"] = people + return Frame( + tables, + frame.schema, + {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={ + **frame.metadata, + "childcare_outside_domain_baseline": { + "engine_version": version("policyengine-us"), + "values": defaults, + "inherited_counts": inherited, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + }, + }, + ) + + +def with_us_childcare_attendance_inputs( + frame: Frame, + *, + household_tsv: str | Path, + calendar_tsv: str | Path, + asec_source_cache: str | Path | None, + seed: int, + inherit_outside_domain_baseline: bool = False, +) -> Frame: + """Run the source/bridge/target recipe and enforce the export contract.""" + contract = childcare_attendance_contract() + spec = SourceStageSpec.from_mapping(contract) + if tuple(spec.outputs) != US_CHILDCARE_ATTENDANCE_COLUMNS: + raise ValueError( + "Childcare source-stage outputs drifted from the engine inputs." + ) + source = load_nsece_childcare(household_tsv, calendar_tsv) + dependence = fit_nsece_sibling_dependence(source.children) + source = bridge_nsece_noncalendar_attendance(source, seed=seed) + normalized = harmonize_asec_childcare_predictors( + frame, source_cache=asec_source_cache + ) + candidate = with_us_nsece_childcare_attendance( + normalized, + source, + seed=seed, + match_columns=NSECE_CHILDCARE_MATCH_COLUMNS, + fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS, + sibling_dependence=dependence["rho"], + ) + if inherit_outside_domain_baseline: + candidate = inherit_outside_domain_attendance_baseline(candidate) + assert_childcare_attendance_exportable(candidate) + return Frame( + {entity: candidate.table(entity) for entity in candidate.entities}, + candidate.schema, + { + entity: candidate.weights_for(entity) + for entity in candidate.weighted_entities + }, + candidate.strata, + mass_log=candidate.mass_log, + metadata={ + **candidate.metadata, + "childcare_attendance_stage": { + "stage": spec.stage, + "outputs": spec.outputs, + "seed": seed, + "operation_order": tuple( + operation.kind for operation in spec.operations + ), + "sibling_dependence": dependence, + "modeled_age_domain": [0, 12], + "outside_domain_policy": "inherit_engine_baseline" + if inherit_outside_domain_baseline + else "require_observed", + }, + }, + ) + + +def export_native_childcare_candidate( + parent_path: str | Path, candidate: Frame, output_path: str | Path +) -> Path: + """Add only attendance inputs to a new native H5, verifying every old column. + + The parent and its period are preserved. This is a local candidate export, + not a source-enrichment release or a publication/certification operation. + """ + from policyengine_us.data import USSingleYearDataset + + parent_path, output_path = Path(parent_path), Path(output_path) + if output_path.exists() or parent_path.resolve() == output_path.resolve(): + raise ValueError("Native childcare output must be a new path.") + assert_childcare_attendance_exportable(candidate) + parent = USSingleYearDataset(file_path=str(parent_path)) + attendance = set(US_CHILDCARE_ATTENDANCE_COLUMNS) + for entity in candidate.entities: + old = getattr(parent, entity) + reference = old.drop(columns=["household_weight"], errors="ignore") + columns = [c for c in reference if c not in attendance] + pd.testing.assert_frame_equal( + canonicalize_table_string_dtypes( + reference[columns], + boundary="native_childcare_export", + table_name=entity, + ), + canonicalize_table_string_dtypes( + candidate.table(entity)[columns], + boundary="native_childcare_export", + table_name=entity, + ), + ) + np.testing.assert_array_equal( + parent.household.household_weight.to_numpy(), + candidate.weights_for("household").values, + ) + people = parent.person.copy() + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + people[column] = candidate.table("person")[column] + output_path.parent.mkdir(parents=True, exist_ok=True) + temporary = output_path.with_name(output_path.name + ".partial.h5") + if temporary.exists(): + raise ValueError("Native childcare temporary path already exists.") + try: + shutil.copyfile(parent_path, temporary) + with pd.HDFStore(temporary, mode="a") as store: + store.put("person", people, format="table", data_columns=True) + store.put( + "_childcare_attendance_receipt", + pd.Series( + [json.dumps(candidate.metadata, default=dict, allow_nan=False)] + ), + ) + loaded = USSingleYearDataset(file_path=str(temporary)) + pd.testing.assert_frame_equal(loaded.person, people) + for entity in candidate.schema.group_entities: + pd.testing.assert_frame_equal( + getattr(loaded, entity), getattr(parent, entity) + ) + if loaded.time_period != parent.time_period: + raise ValueError("Native childcare export changed the parent's period.") + temporary.rename(output_path) + finally: + temporary.unlink(missing_ok=True) + return output_path diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py new file mode 100644 index 000000000..93c0568a5 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py @@ -0,0 +1,213 @@ +"""Harmonize measured ASEC parent relationships for child attendance candidates. + +The source adapter resolves Census PEPAR1/PEPAR2 within physical households. +It never equates all adults with parents. The downstream donor kernel consumes +only normalized age, region and parent-work fields, independent of source spine. +""" + +from __future__ import annotations + +import hashlib +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.us_runtime.childcare_attendance import ( + childcare_attendance_contract, + childcare_income_band, +) +from microcosm.build.us_runtime.education_assistance_source import ( + ASEC_EDUCATION_ASSISTANCE_ARCHIVES, +) +from microcosm.calibrate.geography_constants import ( + US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION, +) +from microcosm.frame import US_SCHEMA, Frame + + +def harmonize_asec_childcare_predictors( + frame: Frame, *, source_cache: str | Path | None = None +) -> Frame: + """Normalize parents of *any* under-13 child, matching NSECE's household unit. + + Census documents PEPAR1/PEPAR2 as parent line numbers and A_LINENO as the + unique person line number. Nonpositive pointers mean no resident parent; + positive pointers must resolve in the same household. Current-week work is + measured by hours_worked_last_week, not annual earnings or usual hours. + https://api.census.gov/data/2025/cps/asec/mar/variables.html + """ + if frame.schema != US_SCHEMA: + raise ValueError("Childcare source harmonization requires a US Frame.") + person = frame.table("person").copy() + required = ( + "person_household_id", + "person_source_id", + "age", + "A_LINENO", + "PEPAR1", + "PEPAR2", + "hours_worked_last_week", + "PTOTVAL", + "source_year", + ) + missing = sorted(set(required) - set(person)) + if missing: + raise ValueError(f"ASEC childcare source fields are missing: {missing}.") + if person.duplicated(["person_household_id", "A_LINENO"]).any(): + raise ValueError("ASEC person line numbers must be unique within households.") + values = person[ + ["age", "A_LINENO", "PEPAR1", "PEPAR2", "hours_worked_last_week"] + ].to_numpy(dtype=float) + if not np.isfinite(values).all() or (values[:, [0, 4]] < 0).any(): + raise ValueError( + "ASEC childcare predictors must be finite with nonnegative age/hours." + ) + if ( + (values[:, :4] % 1 != 0).any() + or (values[:, 1] <= 0).any() + or (values[:, 2:4] < -1).any() + ): + raise ValueError("ASEC ages and parent/person line numbers are invalid.") + young = person.age.between(0, 12) + refs = person.loc[young, ["person_household_id", "PEPAR1", "PEPAR2"]].melt( + id_vars="person_household_id", value_name="A_LINENO" + )[["person_household_id", "A_LINENO"]] + refs = refs.loc[refs.A_LINENO > 0].drop_duplicates() + parents = refs.merge( + person[["person_household_id", "A_LINENO", "hours_worked_last_week"]], + on=["person_household_id", "A_LINENO"], + how="left", + validate="one_to_one", + indicator=True, + ) + if parents._merge.ne("both").any(): + raise ValueError( + "A positive ASEC parent pointer does not resolve within its household." + ) + parents["worked"] = parents.hours_worked_last_week > 0 + counts = parents.groupby("person_household_id").worked.agg(["size", "sum"]) + number = person.person_household_id.map(counts["size"]).fillna(0).to_numpy() + worked = person.person_household_id.map(counts["sum"]).fillna(0).to_numpy() + status = np.select( + [number == 0, worked == 0, worked == number], [-1, 0, 2], default=1 + ) + household = frame.table("household") + if "household_source_id" in household: + identities = household.set_index("household_id").household_source_id + person["childcare_source_household_id"] = person.person_household_id.map( + identities + ).astype(str) + contract = childcare_attendance_contract() + prices = { + int(year): value for year, value in contract["cpi_u_annual_average"].items() + } + source_prices = person.source_year.map(prices) + income_values, income_receipts = _income_source_values(person, source_cache) + if source_prices.isna().any() or not np.isfinite(income_values).all(): + raise ValueError( + "ASEC childcare income needs finite PTOTVAL; supply the pinned ASEC source cache for omitted raw fields." + ) + real_income = ( + income_values * prices[contract["income_reference_year"]] / source_prices + ) + income = real_income.groupby(person.person_household_id).sum() + income_band = childcare_income_band(person.person_household_id.map(income)) + states = household.set_index("household_id").state_fips + region = person.person_household_id.map(states).map( + US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION + ) + if region.isna().any(): + raise ValueError( + "Childcare target contains an unknown household/state/region link." + ) + for name, data in ( + ("parent_work_status", status), + ("region", region.to_numpy()), + ("income_band", income_band), + ): + if name in person and not np.array_equal(person[name].to_numpy(), data): + raise ValueError( + f"Existing childcare predictor {name} disagrees with source harmonization." + ) + person[name] = data + tables = {entity: frame.table(entity) for entity in frame.entities} + tables["person"] = person + return Frame( + tables, + frame.schema, + {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={ + **frame.metadata, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": income_receipts, + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars", + }, + }, + ) + + +def _income_source_values(person, source_cache): + values = person.PTOTVAL.copy() + receipts = [] + if source_cache is None: + return values, receipts + for year in sorted(person.source_year.unique()): + if year not in ASEC_EDUCATION_ASSISTANCE_ARCHIVES: + raise ValueError("No pinned ASEC source covers this income year.") + pin = ASEC_EDUCATION_ASSISTANCE_ARCHIVES[year] + path = Path(source_cache) / pin.member + with path.open("rb") as stream: + digest = hashlib.file_digest(stream, "sha256").hexdigest() + if digest != pin.member_sha256 or path.stat().st_size != pin.member_size_bytes: + raise ValueError("ASEC childcare income source identity mismatch.") + raw = pd.read_csv( + path, + usecols=["PERIDNUM", "PTOTVAL", "A_LINENO", "A_AGE"], + dtype={"PERIDNUM": str}, + ) + if len(raw) != pin.rows or raw.PERIDNUM.duplicated().any(): + raise ValueError( + "ASEC childcare income source person keys/count are invalid." + ) + selected = person.source_year.eq(year) + keys = person.loc[selected, "PERIDNUM"] + if not keys.astype(str).str.fullmatch(r"[0-9]{22}").all(): + raise ValueError( + "ASEC childcare income join requires exact 22-digit PERIDNUM." + ) + joined = raw.set_index("PERIDNUM").reindex(keys) + if ( + joined.isna().any().any() + or not np.array_equal( + joined.A_LINENO.to_numpy(), person.loc[selected, "A_LINENO"].to_numpy() + ) + or not np.array_equal( + joined.A_AGE.to_numpy(), person.loc[selected, "A_AGE"].to_numpy() + ) + ): + raise ValueError( + "ASEC childcare income join fails person/line/age reconciliation." + ) + observed = values.loc[selected].notna().to_numpy() + if not np.array_equal( + values.loc[selected].to_numpy()[observed], + joined.PTOTVAL.to_numpy()[observed], + ): + raise ValueError("Observed ASEC PTOTVAL disagrees with pinned source.") + values.loc[selected] = joined.PTOTVAL.to_numpy() + receipts.append( + { + "income_year": int(year), + "sha256": digest, + "matched_people": int(selected.sum()), + "source_rows": len(raw), + } + ) + return values, receipts diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py index caaec4d22..960e13385 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py @@ -17,25 +17,33 @@ from microcosm.build.us_runtime.childcare_attendance import ( US_CHILDCARE_ATTENDANCE_COLUMNS, + childcare_attendance_contract, + childcare_income_band, impute_us_childcare_attendance, ) from microcosm.frame import US_SCHEMA, Frame, WeightKind, Weights -NSECE_2024_HOUSEHOLD_SHA256 = ( - "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2" -) -NSECE_2024_CALENDAR_SHA256 = ( - "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e" -) +_SOURCE_ARTIFACTS = { + artifact["dataset"]: artifact + for artifact in childcare_attendance_contract()["artifacts"] +} +NSECE_2024_HOUSEHOLD_SHA256 = _SOURCE_ARTIFACTS["DS5"]["sha256"] +NSECE_2024_CALENDAR_SHA256 = _SOURCE_ARTIFACTS["DS4"]["sha256"] NSECE_CHILD_INDICES = tuple(range(1, 10)) NSECE_PROVIDER_INDICES = tuple(range(1, 16)) NSECE_CALENDAR_BLOCKS = 672 -NSECE_CHILDCARE_MATCH_COLUMNS = ("age", "region", "parent_work_status") +NSECE_CHILDCARE_MATCH_COLUMNS = ("age", "region", "parent_work_status", "income_band") +NSECE_CHILDCARE_FALLBACK_COLUMNS = ( + ("age", "parent_work_status", "income_band"), + ("age", "parent_work_status"), + ("age",), +) # HH-280: individual regular paid/unpaid, center, other organizational, irregular. NSECE_ECE_TYPES = frozenset({1, 2, 3, 4, 5, 7}) # HH-566--568: parental care, self-care, or school only. Mixed/unclear gap-check # codes are deliberately unresolved, even if part of the interval involved ECE. -NSECE_NON_ECE_CALENDAR_CODES = frozenset({0, 50, 51, 52, 53, 56, 57, 60, 65, 68, 70}) +NSECE_NON_ECE_CALENDAR_CODES = frozenset({0, 50, 53, 56, 57, 60, 65}) +NSECE_UNPAID_GAP_CODES = frozenset({54, 61, 62, 69}) @dataclass(frozen=True) @@ -47,7 +55,11 @@ class NSECEChildcareSource: source_receipt: dict[str, object] def donors(self) -> tuple[pd.DataFrame, Weights]: - usable = self.children["attendance_status"].eq("complete").to_numpy() + usable = ( + self.children["attendance_status"] + .isin(["complete", "summary_bridge"]) + .to_numpy() + ) return ( self.children.loc[usable].reset_index(drop=True).copy(), Weights(self.weights.values[usable], self.weights.kind), @@ -57,9 +69,11 @@ def donors(self) -> tuple[pd.DataFrame, Weights]: def nsece_childcare_household_columns() -> tuple[str, ...]: return ( "HH4_METH_CASEID", + "HH4_METH_WEIGHT", "HH4_METH_QUEXVERSION", "HH4_REGION", "HH4_PARWORK_STATUS", + "HH4_RPARENT", "HH4_ECON_INCOME_ANNUAL", *( f"{prefix}_{child}" @@ -75,6 +89,11 @@ def nsece_childcare_household_columns() -> tuple[str, ...]: for child in NSECE_CHILD_INDICES for provider in NSECE_PROVIDER_INDICES ), + *( + f"HHC4_NPC_HRSWEEK_TOC{kind}_{child}" + for child in NSECE_CHILD_INDICES + for kind in range(1, 10) + ), ) @@ -139,11 +158,21 @@ def derive_nsece_childcare( raise ValueError("Unknown NSECE calendar completeness code.") ece = np.zeros(values.shape, dtype=bool) known = np.isin(values, tuple(NSECE_NON_ECE_CALENDAR_CODES)) + # HH-314/566: respondent/spouse care depends on parent status. School + # without a provider type is not evidence of K-8 for preschool children. + respondent_parent = rows.HH4_RPARENT.to_numpy() + respondent_care = np.isin(values, [51, 52]) + ece |= np.isin(values, tuple(NSECE_UNPAID_GAP_CODES)) + ece |= respondent_care & (respondent_parent == 0)[:, None] + known |= ece | (respondent_care & (respondent_parent == 1)[:, None]) + known |= (values == 68) & (age_months.loc[present].to_numpy() >= 72)[:, None] + regular_ece = ece.copy() provider_count = np.zeros(len(rows), dtype=int) for p in NSECE_PROVIDER_INDICES: used = values == p ece_type = np.isin(types[:, p - 1], tuple(NSECE_ECE_TYPES)) ece |= used & ece_type[:, None] + regular_ece |= used & np.isin(types[:, p - 1], [1, 2, 3, 4, 5])[:, None] known |= used & (ece_type | (types[:, p - 1] == 6))[:, None] provider_count += used.any(axis=1) & ece_type age = np.floor(age_months.loc[present].to_numpy() / 12) @@ -158,8 +187,21 @@ def derive_nsece_childcare( default="complete", ) complete = reason == "complete" + summary_hours = rows[ + [f"HHC4_NPC_HRSWEEK_TOC{kind}_{child}" for kind in range(1, 10)] + ].to_numpy(dtype=float) + regular_hours = summary_hours[:, :5].sum(axis=1) + summary_known = ( + np.isfinite(summary_hours).all(axis=1) + & (summary_hours >= 0).all(axis=1) + & (summary_hours[:, 7] == 0) + & (regular_hours <= 168) + & (complete | rows.HH4_METH_QUEXVERSION.isin([2, 3]).to_numpy()) + ) days = ece.reshape(-1, 7, 96).any(axis=2).sum(axis=1).astype(float) weekly_hours = ece.sum(axis=1) / 4 + regular_hours = np.where(complete, regular_ece.sum(axis=1) / 4, regular_hours) + summary_known |= complete hours = np.divide(weekly_hours, days, out=np.zeros(len(rows)), where=days > 0) monthly = np.floor(days * 52 / 12 + 0.5) weight = pd.to_numeric(rows[f"HHC4_METH_WEIGHT_{child}"], errors="raise") @@ -173,9 +215,17 @@ def derive_nsece_childcare( "region": rows.HH4_REGION.to_numpy(), "parent_work_status": rows.HH4_PARWORK_STATUS.to_numpy(), "household_income": rows.HH4_ECON_INCOME_ANNUAL.to_numpy(), + "income_band": childcare_income_band(rows.HH4_ECON_INCOME_ANNUAL), "questionnaire_version": rows.HH4_METH_QUEXVERSION.to_numpy(), + "regular_hours_per_week": np.where( + summary_known, regular_hours, np.nan + ), + "irregular_hours_per_week": np.where( + complete, weekly_hours - regular_hours, np.nan + ), "attendance_status": reason, "child_weight": weight.to_numpy(), + "household_weight": rows.HH4_METH_WEIGHT.to_numpy(), "ece_provider_count": np.where(complete, provider_count, np.nan), "ece_hours_per_week": np.where(complete, weekly_hours, np.nan), } @@ -239,6 +289,8 @@ def with_us_nsece_childcare_attendance( *, seed: int, match_columns: tuple[str, ...], + fallback_match_columns: tuple[tuple[str, ...], ...] = (), + sibling_dependence: float = 0.0, ) -> Frame: """Apply the source to a candidate Frame, preserving links, weights and receipts. @@ -251,17 +303,30 @@ def with_us_nsece_childcare_attendance( if frame.schema != US_SCHEMA: raise ValueError("NSECE childcare attendance requires the US schema.") donor, weights = source.donors() + original_people = frame.table("person") + recipients = original_people.copy() + # Native BuildP IDs are exact int64, while the pure donor API uses strings. + # Encode integers losslessly for hashing, then restore the native column. + if "person_source_id" in recipients and pd.api.types.is_integer_dtype( + recipients.person_source_id + ): + if recipients.person_source_id.isna().any(): + raise ValueError("Childcare source person IDs cannot be missing.") + recipients["person_source_id"] = recipients.person_source_id.astype(str) people = impute_us_childcare_attendance( - frame.table("person"), + recipients, donor, donor_weights=weights, seed=seed, match_columns=match_columns, + fallback_match_columns=fallback_match_columns, + sibling_dependence=sibling_dependence, ) # Float storage supports unresolved nulls in ordinary Frame checkpoints; # the monthly variable has already been validated to be integral when known. for column in US_CHILDCARE_ATTENDANCE_COLUMNS: people[column] = people[column].astype(float) + people["person_source_id"] = original_people.person_source_id tables = {entity: frame.table(entity).copy() for entity in frame.entities} tables["person"] = people return Frame( @@ -276,6 +341,8 @@ def with_us_nsece_childcare_attendance( **source.source_receipt, "seed": int(seed), "match_columns": match_columns, + "fallback_match_columns": fallback_match_columns, + "sibling_dependence": sibling_dependence, "candidate_only": True, }, }, @@ -313,6 +380,10 @@ def nsece_childcare_validation_report( Repeated use of this fixed holdout does not create fresh independent evidence. """ children = source.children + if children.attendance_status.eq("summary_bridge").any(): + raise ValueError( + "Validate original calendars before bridge completion to avoid leakage." + ) donors, _ = source.donors() in_domain = children.age.between(0, 12) domain_mass = float(children.loc[in_domain, "child_weight"].sum()) diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_assessment.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_assessment.py new file mode 100644 index 000000000..d73dbd41f --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_assessment.py @@ -0,0 +1,292 @@ +"""Household cross-validation and questionnaire transport checks for attendance. + +These checks expose selection and sparse support. Conditional donor expectations +are used for prediction scoring so a single stochastic draw cannot hide bias. +They do not identify unobserved attendance under nonrandom calendar missingness. +""" + +from __future__ import annotations + +import hashlib + +import numpy as np +import pandas as pd + +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, + NSECE_CHILDCARE_MATCH_COLUMNS, + NSECEChildcareSource, +) +from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_dependence import ( + complete_sibling_pairs, + fit_nsece_sibling_dependence, +) + + +def _conditional_expectations(train, target, outcomes): + """Weighted empirical expectation with a declared, age-preserving hierarchy.""" + result = pd.DataFrame(np.nan, index=target.index, columns=outcomes) + levels = pd.Series("unsupported", index=target.index, dtype="string") + for columns in (NSECE_CHILDCARE_MATCH_COLUMNS, *NSECE_CHILDCARE_FALLBACK_COLUMNS): + weighted = train.reindex(columns=columns).copy() + weighted["mass"] = train.child_weight + for outcome in outcomes: + weighted[outcome] = ( + train.reindex(columns=[outcome]).iloc[:, 0] * train.child_weight + ) + grouped = weighted.groupby(list(columns)).sum() + means = grouped[outcomes].div(grouped.mass, axis=0) + ids = levels.index[levels == "unsupported"] + keys = ( + pd.MultiIndex.from_frame(target.reindex(index=ids, columns=columns)) + if len(columns) > 1 + else target.reindex(index=ids, columns=columns).iloc[:, 0] + ) + values = means.reindex(keys).to_numpy() + supported = np.isfinite(values).all(axis=1) + result.loc[ids[supported]] = values[supported] + levels.loc[ids[supported]] = ",".join(columns) + return result, levels + + +def assess_nsece_childcare(source: NSECEChildcareSource, *, seed: int = 271828) -> dict: + children = source.children.loc[source.children.age.between(0, 12)].copy() + usable = children.attendance_status.eq("complete") + coverage = [] + for grouping in ( + "questionnaire_version", + "age", + "region", + "parent_work_status", + "income_band", + ): + for label, indices in children.groupby(grouping).groups.items(): + g = children.loc[indices] + coverage.append( + { + "grouping": grouping, + "group": str(label), + "n": len(g), + "weight": float(g.child_weight.sum()), + "usable_weight_share": float( + g.loc[usable.loc[indices], "child_weight"].sum() + / g.child_weight.sum() + ), + } + ) + donors = children.loc[usable].copy() + donors["participation"] = (donors.childcare_days_per_week > 0).astype(float) + donors["days"] = donors.childcare_days_per_week + donors["hours"] = donors.ece_hours_per_week + outcomes = ["participation", "days", "hours"] + folds = donors.source_household_id.map( + lambda x: ( + int.from_bytes(hashlib.sha256(f"{seed}:{x}".encode()).digest()[:8], "big") + % 5 + ) + ) + prediction = pd.DataFrame(np.nan, index=donors.index, columns=outcomes) + levels = pd.Series("", index=donors.index, dtype="string") + sibling_rows = [] + for fold in range(5): + train, target = donors.loc[folds != fold], donors.loc[folds == fold] + if train.empty or target.empty: + raise ValueError("Five-fold assessment requires households in every fold") + if not set(train.source_household_id).isdisjoint(target.source_household_id): + raise ValueError("Childcare assessment leaks households across folds") + predicted, matched = _conditional_expectations(train, target, outcomes) + prediction.loc[target.index] = predicted + levels.loc[target.index] = matched + heldout_households = set(target.source_household_id) + fitted = fit_nsece_sibling_dependence( + children.loc[~children.source_household_id.isin(heldout_households)] + ) + pairs = complete_sibling_pairs( + children.loc[children.source_household_id.isin(heldout_households)] + ) + if not pairs.empty: + probabilities = ( + predicted.loc[pairs.index, "participation"].to_numpy().reshape(-1, 2) + ) + actual = ( + (pairs.childcare_days_per_week.to_numpy() > 0) + .reshape(-1, 2) + .all(axis=1) + ) + weights = pairs.household_weight.to_numpy().reshape(-1, 2)[:, 0] + independent = probabilities.prod(axis=1) + coupled = (1 - fitted["rho"]) * independent + fitted[ + "rho" + ] * probabilities.min(axis=1) + sibling_rows.append( + { + "fold": fold, + "training_rho": fitted["rho"], + "households": len(weights), + "weight": float(weights.sum()), + "observed_both": float(np.average(actual, weights=weights)), + "independent_both": float(np.average(independent, weights=weights)), + "coupled_both": float(np.average(coupled, weights=weights)), + } + ) + comparisons = [] + for grouping in (None, "age", "region", "parent_work_status", "income_band"): + groups = ( + [("all", donors.index)] + if grouping is None + else donors.groupby(grouping).groups.items() + ) + for label, indices in groups: + g = donors.loc[indices] + comparisons.append( + { + "grouping": grouping or "all", + "group": str(label), + "n": len(g), + "observed": { + c: float(np.average(g[c], weights=g.child_weight)) + for c in outcomes + }, + "expected": { + c: float( + np.average( + prediction.loc[indices, c], weights=g.child_weight + ) + ) + for c in outcomes + }, + } + ) + # Independent questionnaire instruments have regular-care weekly hours but + # no days. Compare the common regular-care estimand, not all-ECE totals. + regular = children.loc[children.regular_hours_per_week.notna()].copy() + regular["regular_participation"] = (regular.regular_hours_per_week > 0).astype( + float + ) + regular_outcomes = ["regular_participation", "regular_hours_per_week"] + transport = [] + for questionnaire in (2, 3): + target = regular.loc[regular.questionnaire_version == questionnaire] + train = regular.loc[ + (regular.questionnaire_version == 1) + & regular.attendance_status.eq("complete") + ] + if target.empty or train.empty: + continue + predicted, matched = _conditional_expectations(train, target, regular_outcomes) + supported = matched != "unsupported" + target, predicted = target.loc[supported], predicted.loc[supported] + transport.append( + { + "questionnaire_version": questionnaire, + "scored_children": len(target), + "unsupported_children": int((~supported).sum()), + "observed": { + c: float(np.average(target[c], weights=target.child_weight)) + for c in regular_outcomes + }, + "expected": { + c: float(np.average(predicted[c], weights=target.child_weight)) + for c in regular_outcomes + }, + } + ) + return { + "source": source.source_receipt, + "seed": seed, + "design": "five-fold household-separated conditional-expectation diagnostic", + "coverage": coverage, + "cross_validation": comparisons, + "matching_levels": levels.value_counts().to_dict(), + "sibling_validation": sibling_rows, + "questionnaire_transport": transport, + "assumptions": [ + "Calendar selection is ignorable conditional on matching fields; not identified from these data.", + "Main reference-week schedules transfer to May/fall typical weeks; summer attendance is not observed.", + "Regular-care comparisons cannot validate days or irregular care in instruments without calendars.", + ], + "production_ready": False, + } + + +def assess_noncalendar_bridge( + source: NSECEChildcareSource, *, seed: int = 161803 +) -> dict: + """Mask whole-household calendars, keeping only the observed regular hours.""" + children = source.children.copy() + complete = children.attendance_status.eq("complete") + holdout = complete & children.source_household_id.map( + lambda x: ( + int.from_bytes(hashlib.sha256(f"{seed}:{x}".encode()).digest()[:8], "big") + % 5 + == 0 + ) + ) + truth = children.loc[holdout].copy() + children.loc[~complete, "regular_hours_per_week"] = np.nan + children.loc[holdout, "attendance_status"] = "missing_calendar" + children.loc[holdout, "questionnaire_version"] = 2 + children.loc[ + holdout, + [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day", + "ece_hours_per_week", + "irregular_hours_per_week", + ], + ] = np.nan + bridged = bridge_nsece_noncalendar_attendance( + NSECEChildcareSource(children, source.weights, source.source_receipt), seed=seed + ) + predicted = bridged.children.loc[holdout] + if not predicted.attendance_status.eq("summary_bridge").all(): + raise ValueError("Masked-calendar bridge has unsupported records") + errors = [] + for group in (None, "age", "parent_work_status"): + groups = ( + [("all", truth.index)] + if group is None + else truth.groupby(group).groups.items() + ) + for label, indices in groups: + w = truth.loc[indices, "child_weight"] + errors.append( + { + "grouping": group or "all", + "group": str(label), + "n": len(indices), + "observed_days": float( + np.average( + truth.loc[indices, "childcare_days_per_week"], weights=w + ) + ), + "imputed_days": float( + np.average( + predicted.loc[indices, "childcare_days_per_week"], weights=w + ) + ), + "observed_hours": float( + np.average(truth.loc[indices, "ece_hours_per_week"], weights=w) + ), + "imputed_hours": float( + np.average( + predicted.loc[indices, "ece_hours_per_week"], weights=w + ) + ), + } + ) + return { + "seed": seed, + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "comparisons": errors, + "regular_hours_preserved": bool( + np.array_equal( + predicted.regular_hours_per_week, truth.regular_hours_per_week + ) + ), + "limitations": "Tests reconstruction where calendars exist; not a direct test of unobserved days in the other questionnaire instruments.", + } diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py new file mode 100644 index 000000000..471dcb43c --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py @@ -0,0 +1,124 @@ +"""Complete noncalendar schedules conditional on measured regular-care hours. + +NSECE HH-10/11 describes the summer/fall instruments: regular weekly hours are +observed, days and irregular arrangements are not collected. Preserve those +hours and impute days/irregular intensity jointly from a compatible calendar. +Zero regular hours do not establish zero irregular care. No source observation +is replaced; completed rows have separate status and donor provenance. +""" + +from __future__ import annotations + +import hashlib + +import numpy as np + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + childcare_attendance_contract, +) +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, + NSECE_CHILDCARE_MATCH_COLUMNS, + NSECEChildcareSource, +) + + +def bridge_nsece_noncalendar_attendance( + source: NSECEChildcareSource, *, seed: int +) -> NSECEChildcareSource: + """Use nearest regular-hour donors, retaining all ties at the cutoff. + + All matching levels require the same regular-care participation status. + Hours cannot exceed 24 per attended day or 168 per week. The parent source + remains unchanged. Synthetic validation must mask whole households before + this function is called; it never learns from completed bridge records. + """ + children = source.children.copy() + nearest_donors = childcare_attendance_contract()["operations"][1]["nearest_donors"] + pool = ( + children.loc[ + children.attendance_status.eq("complete") + & children.regular_hours_per_week.notna() + ] + .sort_values("donor_id") + .copy() + ) + pool["has_regular_care"] = pool.regular_hours_per_week > 0 + target = children.loc[ + children.age.between(0, 12) + & children.attendance_status.eq("missing_calendar") + & children.questionnaire_version.isin([2, 3]) + & children.regular_hours_per_week.notna() + ] + unsupported = 0 + month, days_column, hours_column = US_CHILDCARE_ATTENDANCE_COLUMNS + for index, child in target.iterrows(): + candidates = pool.iloc[:0] + for level in (NSECE_CHILDCARE_MATCH_COLUMNS, *NSECE_CHILDCARE_FALLBACK_COLUMNS): + mask = pool.has_regular_care.eq(child.regular_hours_per_week > 0) + for column in level: + mask &= pool[column].eq(child[column]) + candidates = pool.loc[ + mask + & ( + (pool.irregular_hours_per_week + child.regular_hours_per_week) + <= 168 + ) + ] + if not candidates.empty: + break + if candidates.empty: + unsupported += 1 + continue + candidates = candidates.assign( + distance=np.abs( + np.log1p(candidates.regular_hours_per_week) + - np.log1p(child.regular_hours_per_week) + ) + ) + candidates = candidates.sort_values(["distance", "donor_id"]) + cutoff = candidates.distance.iloc[min(nearest_donors, len(candidates)) - 1] + candidates = candidates.loc[candidates.distance <= cutoff] + weights = candidates.child_weight.to_numpy() + cumulative = np.cumsum(weights / weights.sum()) + digest = hashlib.sha256( + f"{seed}:nsece_bridge:{child.donor_id}".encode() + ).digest() + draw = (int.from_bytes(digest[:8], "big") >> 11) / 2**53 + donor = candidates.iloc[ + min( + int(np.searchsorted(cumulative, draw, side="right")), + len(candidates) - 1, + ) + ] + total_hours = child.regular_hours_per_week + donor.irregular_hours_per_week + days = max(float(donor[days_column]), float(np.ceil(total_hours / 24))) + if total_hours == 0: + days = 0.0 + children.loc[index, [month, days_column, hours_column]] = [ + np.floor(days * 52 / 12 + 0.5), + days, + total_hours / days if days else 0.0, + ] + children.loc[index, "ece_hours_per_week"] = total_hours + children.loc[index, "irregular_hours_per_week"] = donor.irregular_hours_per_week + children.loc[index, "attendance_status"] = "summary_bridge" + children.loc[index, "schedule_bridge_donor"] = donor.donor_id + children.loc[index, "schedule_bridge_match"] = ",".join(level) + completed = children.attendance_status.eq("summary_bridge") + return NSECEChildcareSource( + children, + source.weights, + { + **source.source_receipt, + "noncalendar_bridge": { + "seed": seed, + "completed_children": int(completed.sum()), + "unsupported_children": unsupported, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", + }, + }, + ) diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py new file mode 100644 index 000000000..932e1afe0 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py @@ -0,0 +1,80 @@ +"""Fit a household shared-rank mixture from measured sibling participation. + +The mixture retains each child's weighted conditional donor distribution. With +probability rho, siblings use a shared uniform rank in care-sorted donor pools; +otherwise they draw independently. It interpolates the pair's joint positive +probability between p1*p2 and min(p1,p2). This models dependence, not shared +provider identity, and must be checked on household-held-out pairs. +""" + +from __future__ import annotations + +import numpy as np + +from microcosm.build.us_runtime.nsece_childcare import NSECE_CHILDCARE_MATCH_COLUMNS + + +def complete_sibling_pairs(children): + children = children.loc[children.age.between(0, 12)].copy() + counts = children.groupby("source_household_id").attendance_status.agg( + lambda x: len(x) >= 2 and x.eq("complete").all() + ) + selected = children.source_household_id.isin(counts.index[counts]) + # One pair per household avoids giving large sibships disproportionate mass. + return ( + children.loc[selected] + .sort_values(["source_household_id", "age", "donor_id"]) + .groupby("source_household_id", sort=False) + .head(2) + ) + + +def fit_nsece_sibling_dependence(children) -> dict: + pool = children.loc[children.attendance_status.eq("complete")].copy() + pool["weighted_care"] = (pool.childcare_days_per_week > 0) * pool.child_weight + cells = pool.groupby(list(NSECE_CHILDCARE_MATCH_COLUMNS))[ + ["weighted_care", "child_weight"] + ].sum() + cells["probability"] = cells.weighted_care / cells.child_weight + pairs = complete_sibling_pairs(children) + if pairs.empty: + return {"rho": 0.0, "households": 0, "status": "no measured sibling pairs"} + predicted = ( + pairs[list(NSECE_CHILDCARE_MATCH_COLUMNS)] + .merge( + cells[["probability"]], + left_on=list(NSECE_CHILDCARE_MATCH_COLUMNS), + right_index=True, + how="left", + validate="many_to_one", + ) + .probability.to_numpy() + .reshape(-1, 2) + ) + actual = (pairs.childcare_days_per_week.to_numpy() > 0).reshape(-1, 2) + weights = pairs.household_weight.to_numpy().reshape(-1, 2) + if ( + not np.isfinite(weights).all() + or (weights <= 0).any() + or not np.array_equal(weights[:, 0], weights[:, 1]) + ): + raise ValueError( + "Sibling dependence requires consistent positive household design weights." + ) + weights = weights[:, 0] + independent = predicted.prod(axis=1) + shared = predicted.min(axis=1) + observed = actual.all(axis=1) + denominator = float((shared - independent) @ weights) + unconstrained = ( + float((observed - independent) @ weights) / denominator if denominator else 0.0 + ) + return { + "rho": float(np.clip(unconstrained, 0, 1)), + "unconstrained_rho": unconstrained, + "households": len(weights), + "observed_both_in_care": float(np.average(observed, weights=weights)), + "independent_both_in_care": float(np.average(independent, weights=weights)), + "shared_both_in_care": float(np.average(shared, weights=weights)), + "estimation": "youngest pair in fully observed households; household design weights", + } diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py b/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py index 5f14c63e5..94d358338 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py @@ -51,6 +51,9 @@ US_CAPITAL_GAIN_DETAILS_OUTPUT_COLUMNS, ) from microcosm.build.us_runtime.child_support import US_CHILD_SUPPORT_OUTPUT_COLUMNS +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) from microcosm.build.us_runtime.disability_benefits import ( US_DISABILITY_BENEFITS_OUTPUT_COLUMNS, ) @@ -140,6 +143,7 @@ # become structural zeroes. POST_REFERENCE_ECPS_REQUIRED_INPUTS = frozenset( { + *US_CHILDCARE_ATTENDANCE_COLUMNS, "fsla_overtime_premium", "qualified_passenger_vehicle_loan_interest", "traditional_401k_contributions_desired", @@ -634,12 +638,8 @@ def _ecps_populated_layers() -> frozenset[str]: f"{_ECPS_PARITY_REFERENCE_RESOURCE}: 'nonzero_shares' must be a " "non-empty JSON object." ) - historical = { - str(name) for name, share in shares.items() if float(share) > 0.0 - } - projected = { - REFERENCE_ECPS_LAYER_RENAMES.get(name, name) for name in historical - } + historical = {str(name) for name, share in shares.items() if float(share) > 0.0} + projected = {REFERENCE_ECPS_LAYER_RENAMES.get(name, name) for name in historical} if len(projected) != len(historical): raise ValueError( f"{_ECPS_PARITY_REFERENCE_RESOURCE}: reference-layer rename " diff --git a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py index f0f302358..04ddb3149 100644 --- a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py +++ b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py @@ -12295,3 +12295,19 @@ def _ancestor_if_tests(node: ast.AST) -> list[str]: assert len(owner_check_calls) == 5, ( f"expected 5 owner-resolution sites in _main(), found {len(owner_check_calls)}" ) + + +@pytest.mark.parametrize( + "options", + [ + ["--childcare-attendance-household-tsv", "household.tsv"], + ["--childcare-attendance-calendar-tsv", "calendar.tsv"], + ["--childcare-attendance-inherit-outside-domain-baseline"], + ], +) +def test_attendance_source_cli_requires_paired_inputs(options): + builder = _load_builder_module() + with pytest.raises(SystemExit): + builder._parse_args( + ["--ledger-facts", "facts.jsonl", "--out", "release", *options] + ) diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index efb2d1871..812470a04 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -43,6 +43,8 @@ def _raw(n=1): household["HH4_METH_QUEXVERSION"] = 1 household["HH4_REGION"] = 1 household["HH4_PARWORK_STATUS"] = 2 + household["HH4_RPARENT"] = 1 + household["HH4_METH_WEIGHT"] = 100.0 household["HH4_ECON_INCOME_ANNUAL"] = 40_000 for child in NSECE_CHILD_INDICES: household[f"HHC4_METH_WEIGHT_{child}"] = np.nan @@ -272,3 +274,377 @@ def checked(recipient, donor, **kwargs): assert report["production_ready"] is False assert report["under13_complete_weight_share"] == pytest.approx(29 / 30) assert report["comparisons"][0]["observed"] == report["comparisons"][0]["predicted"] + + +@pytest.mark.parametrize( + "code,parent,age,expected", + [ + (51, 1, 36, 0), + (51, 0, 36, 1), + (52, -1, 36, None), + (54, 1, 36, 1), + (61, 1, 36, 1), + (68, 1, 36, None), + (68, 1, 84, 0), + (70, 0, 36, None), + ], +) +def test_gap_care_uses_parent_status_and_school_age(code, parent, age, expected): + hh, cal = _raw() + hh["HH4_RPARENT"] = parent + hh["HHC4_AGE_AT_USAGE_1"] = age + cal.loc[0, "HH4_CHCAL_R_1_1"] = code + child = derive_nsece_childcare(hh, cal).children.iloc[0] + if expected is None: + assert pd.isna(child[DAYS]) + else: + assert child[DAYS] == expected + + +def _asec_frame(): + frame = _frame() + tables = {e: frame.table(e).copy() for e in frame.entities} + tables["person"]["A_LINENO"] = [1, 2] + tables["person"]["PEPAR1"] = [-1, 1] + tables["person"]["PEPAR2"] = [-1, -1] + tables["person"]["hours_worked_last_week"] = [40, 0] + tables["person"]["PTOTVAL"] = [40000, 0] + tables["person"]["source_year"] = 2023 + tables["household"]["state_fips"] = 25 + return Frame( + tables, + frame.schema, + {"household": frame.weights_for("household")}, + metadata=frame.metadata, + ) + + +def test_asec_parent_work_and_region_come_from_measured_relationships(): + from microcosm.build.us_runtime.childcare_population import ( + harmonize_asec_childcare_predictors, + ) + + before = _asec_frame() + result = harmonize_asec_childcare_predictors(before) + assert result.table("person").parent_work_status.tolist() == [2, 2] + assert result.table("person").region.tolist() == [1, 1] + assert "parent_work_status" not in before.table("person") + assert result.metadata["existing_receipt"] == "preserved" + # A working adult without a parent link must not count as a working parent. + before.table("person")["PEPAR1"] = -1 + result = harmonize_asec_childcare_predictors(before) + assert result.table("person").parent_work_status.tolist() == [-1, -1] + + +def test_asec_dangling_parent_pointer_is_refused(): + from microcosm.build.us_runtime.childcare_population import ( + harmonize_asec_childcare_predictors, + ) + + frame = _asec_frame() + frame.table("person").loc[1, "PEPAR1"] = 99 + with pytest.raises(ValueError, match="does not resolve"): + harmonize_asec_childcare_predictors(frame) + + +def test_integer_source_ids_are_losslessly_encoded_and_restored(): + frame = _frame() + frame.table("person")["person_source_id"] = [2**60, 2**60 + 1] + result = with_us_nsece_childcare_attendance( + frame, _source(), seed=915, match_columns=("age",) + ) + pd.testing.assert_series_equal( + result.table("person").person_source_id, frame.table("person").person_source_id + ) + assert result.table("person")[DAYS].tolist() == [0, 5] + + +def test_sparse_matching_is_explicit_and_keeps_joint_schedule(): + from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, + NSECE_CHILDCARE_MATCH_COLUMNS, + ) + + frame = _frame() + frame.table("person")["region"] = 4 + frame.table("person")["parent_work_status"] = 0 + frame.table("person")["income_band"] = 2 + with pytest.raises(ValueError, match="No compatible"): + with_us_nsece_childcare_attendance( + frame, _source(), seed=915, match_columns=NSECE_CHILDCARE_MATCH_COLUMNS + ) + result = with_us_nsece_childcare_attendance( + frame, + _source(), + seed=915, + match_columns=NSECE_CHILDCARE_MATCH_COLUMNS, + fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS, + ) + assert result.table("person").loc[1, "childcare_attendance_match_level"] == "age" + assert result.table("person").loc[1, [MONTH, DAYS, HOURS]].tolist() == [22, 5, 8] + + +def test_census_region_map_covers_all_states_once(): + from microcosm.calibrate.geography_constants import ( + US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION, + US_STATE_NUMERIC_FIPS_TO_POSTAL, + ) + + assert set(US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION) == set( + US_STATE_NUMERIC_FIPS_TO_POSTAL + ) + assert US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION[11] == 3 + assert US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION[17] == 2 + assert US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION[6] == 4 + + +def test_noncalendar_bridge_preserves_measured_hours_without_asserting_days_observed(): + from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, + ) + + hh, cal = _raw(2) + for day in range(5): + _care(cal, row=0, day=day, hours=8) + hh.loc[1, "HH4_METH_QUEXVERSION"] = 2 + hh.loc[1, "HH4_MISSING_STATUS_CC_1"] = 0 + for kind in range(1, 10): + hh.loc[1, f"HHC4_NPC_HRSWEEK_TOC{kind}_1"] = 0 + hh.loc[1, "HHC4_NPC_HRSWEEK_TOC4_1"] = 12 + source = derive_nsece_childcare(hh, cal) + result = bridge_nsece_noncalendar_attendance(source, seed=915) + child = result.children.iloc[1] + assert child.attendance_status == "summary_bridge" + assert child.regular_hours_per_week == 12 + assert child[DAYS] == 5 + assert child[HOURS] == pytest.approx(2.4) + assert child.ece_hours_per_week == 12 + assert pd.isna(source.children.iloc[1][DAYS]) + assert len(result.donors()[0]) == 2 + with pytest.raises(ValueError, match="before bridge"): + nsece_childcare_validation_report(result) + + +def test_zero_regular_care_does_not_erase_unmeasured_irregular_care(): + from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, + ) + + hh, cal = _raw(2) + hh.loc[0, "HH4_TYPEOFCARE_AGG_1_1"] = 7 + _care(cal, row=0, hours=2) + hh.loc[1, "HH4_METH_QUEXVERSION"] = 3 + hh.loc[1, "HH4_MISSING_STATUS_CC_1"] = 0 + for kind in range(1, 10): + hh.loc[1, f"HHC4_NPC_HRSWEEK_TOC{kind}_1"] = 0 + result = bridge_nsece_noncalendar_attendance( + derive_nsece_childcare(hh, cal), seed=915 + ) + assert result.children.iloc[1].regular_hours_per_week == 0 + assert result.children.iloc[1].irregular_hours_per_week == 2 + assert result.children.iloc[1][DAYS] == 1 + + +def test_shared_household_rank_preserves_sibling_and_clone_coherence(): + from microcosm.build.us_runtime.childcare_attendance import ( + impute_us_childcare_attendance, + ) + + donor = pd.DataFrame( + { + "donor_id": ["a", "b"], + "age": [3, 3], + MONTH: [0, 22], + DAYS: [0, 5], + HOURS: [0, 8], + } + ) + recipients = pd.DataFrame( + { + "person_source_id": ["a", "b", "a", "b"], + "childcare_source_household_id": ["family"] * 4, + "age": [3] * 4, + } + ) + result = impute_us_childcare_attendance( + recipients, + donor, + donor_weights=Weights(np.array([1.0, 1.0]), WeightKind.DESIGN), + match_columns=("age",), + seed=915, + sibling_dependence=1, + ) + assert result[DAYS].nunique() == 1 + assert result[HOURS].nunique() == 1 + + +def test_source_stage_contract_pins_outputs_and_assets(): + from microcosm.build.source_manifest import SourceStageSpec + from microcosm.build.us_runtime.childcare_attendance import ( + childcare_attendance_contract, + childcare_income_band, + ) + from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_2024_CALENDAR_SHA256, + NSECE_2024_HOUSEHOLD_SHA256, + ) + + contract = childcare_attendance_contract() + assert ( + SourceStageSpec.from_mapping(contract).outputs + == US_CHILDCARE_ATTENDANCE_COLUMNS + ) + assert [x["sha256"] for x in contract["artifacts"]] == [ + NSECE_2024_HOUSEHOLD_SHA256, + NSECE_2024_CALENDAR_SHA256, + ] + assert childcare_income_band([0, 25000, 25001, 200000, 200001]).tolist() == [ + 0, + 1, + 2, + 4, + 5, + ] + + +@pytest.mark.requires_us +def test_native_export_explicitly_inherits_outside_baseline_and_preserves_parent( + tmp_path, +): + from policyengine_us.data import USSingleYearDataset + + from microcosm.build.us_runtime.childcare_attendance_stage import ( + export_native_childcare_candidate, + inherit_outside_domain_attendance_baseline, + ) + + frame = _frame(parent_observed=False) + tables = {e: frame.table(e).copy() for e in frame.entities} + tables["household"]["household_weight"] = frame.weights_for("household").values + parent = tmp_path / "parent.h5" + USSingleYearDataset(**tables, time_period=2026).save(str(parent)) + original = parent.read_bytes() + candidate = with_us_nsece_childcare_attendance( + frame, _source(), seed=915, match_columns=("age",) + ) + from microcosm.build.serialization_dtypes import canonicalize_frame_string_dtypes + + filled = inherit_outside_domain_attendance_baseline( + canonicalize_frame_string_dtypes( + candidate, boundary="test_childcare_native_export" + ) + ) + assert ( + filled.table("person").loc[0, f"{DAYS}_source"] + == "inherited_engine_baseline_outside_age_0_12" + ) + assert filled.table("person").loc[1, DAYS] == 5 + path = export_native_childcare_candidate(parent, filled, tmp_path / "candidate.h5") + result = USSingleYearDataset(file_path=str(path)) + assert result.person[DAYS].tolist() == [0, 5] + assert result.time_period == "2026" + assert parent.read_bytes() == original + + +def test_asec_income_sidecar_checks_identity_and_preserves_raw_missingness( + tmp_path, monkeypatch +): + import hashlib + from types import SimpleNamespace + + from microcosm.build.us_runtime import childcare_population as population + + frame = _asec_frame() + people = frame.table("person") + people["PERIDNUM"] = ["1000000000000000000001", "1000000000000000000002"] + people["A_AGE"] = people.age + people["PTOTVAL"] = [np.nan, 0.0] + raw = people[["PERIDNUM", "A_AGE", "A_LINENO", "PTOTVAL"]].copy() + raw.loc[0, "PTOTVAL"] = 40000 + path = tmp_path / "source.csv" + raw.to_csv(path, index=False) + monkeypatch.setattr( + population, + "ASEC_EDUCATION_ASSISTANCE_ARCHIVES", + { + 2023: SimpleNamespace( + member=path.name, + member_sha256=hashlib.sha256(path.read_bytes()).hexdigest(), + member_size_bytes=path.stat().st_size, + rows=2, + ) + }, + ) + with pytest.raises(ValueError, match="finite PTOTVAL"): + population.harmonize_asec_childcare_predictors(frame) + result = population.harmonize_asec_childcare_predictors( + frame, source_cache=tmp_path + ) + assert result.table("person").income_band.tolist() == [2, 2] + assert pd.isna(result.table("person").loc[0, "PTOTVAL"]) + people.loc[1, "PTOTVAL"] = 1 + with pytest.raises(ValueError, match="disagrees with pinned source"): + population.harmonize_asec_childcare_predictors(frame, source_cache=tmp_path) + path.write_text("corrupt") + with pytest.raises(ValueError, match="identity mismatch"): + population.harmonize_asec_childcare_predictors(frame, source_cache=tmp_path) + + +@pytest.mark.requires_us +def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch): + from microcosm.build.us_runtime import childcare_attendance_stage as stage + + frame = _asec_frame() + frame.table("household")["household_source_id"] = "family" + hh, cal = _raw() + hh["HHC4_AGE_AT_USAGE_1"] = frame.table("person").loc[1, "age"] * 12 + hh["HH4_REGION"] = 1 + hh["HH4_PARWORK_STATUS"] = 2 + hh["HH4_ECON_INCOME_ANNUAL"] = 40000 + _care(cal, hours=3) + source = derive_nsece_childcare(hh, cal) + monkeypatch.setattr(stage, "load_nsece_childcare", lambda *args: source) + result = stage.with_us_childcare_attendance_inputs( + frame, + household_tsv="synthetic", + calendar_tsv="synthetic", + asec_source_cache=None, + seed=915, + inherit_outside_domain_baseline=True, + ) + assert result.table("person").loc[1, DAYS] == 1 + assert result.table("person").loc[1, HOURS] == 3 + assert ( + result.metadata["childcare_attendance_stage"]["stage"] + == "nsece_childcare_attendance" + ) + assert result.metadata["existing_receipt"] == "preserved" + np.testing.assert_array_equal( + result.weights_for("household").values, frame.weights_for("household").values + ) + + +def test_bridge_retains_all_equally_near_donors(): + from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, + ) + + hh, cal = _raw(13) + # Eleven donors have zero regular hours. Household 9 sorts last by ID + # and has irregular care and substantial weight; truncating equal-distance + # donors by their IDs would incorrectly erase that care distribution. + hh.loc[8, "HH4_TYPEOFCARE_AGG_1_1"] = 7 + hh.loc[8, "HHC4_METH_WEIGHT_1"] = 1e12 + _care(cal, row=8, hours=2) + for row in (11, 12): + hh.loc[row, "HH4_METH_QUEXVERSION"] = 3 + hh.loc[row, "HH4_MISSING_STATUS_CC_1"] = 0 + for kind in range(1, 10): + hh.loc[row, f"HHC4_NPC_HRSWEEK_TOC{kind}_1"] = 0 + source = derive_nsece_childcare(hh, cal) + result = bridge_nsece_noncalendar_attendance(source, seed=915) + bridged = result.children.loc[ + result.children.attendance_status.eq("summary_bridge") + ] + assert len(bridged) == 2 + assert bridged.irregular_hours_per_week.eq(2).all() diff --git a/packages/microcosm-build/tests/test_us_spine_blindness.py b/packages/microcosm-build/tests/test_us_spine_blindness.py index 740761dc2..dbe124cd2 100644 --- a/packages/microcosm-build/tests/test_us_spine_blindness.py +++ b/packages/microcosm-build/tests/test_us_spine_blindness.py @@ -234,6 +234,8 @@ # Opt-in normalized-donor preparation; not a registered build stage. # Still scanned for source-spine access by the all-runtime guard. "childcare_attendance.py", + "childcare_attendance_stage.py", # Licensed source extension after relationship/hours inputs. + "childcare_population.py", # ASEC relationship harmonization for candidate builds. "congressional_district_geography.py", "congressional_district_vintage.py", "congressional_district_vintage_crosswalk.py", @@ -256,6 +258,9 @@ "nonzero_shares.py", # Hash-verified local NSECE source adapter and opt-in candidate Frame step. "nsece_childcare.py", + "nsece_childcare_assessment.py", # Source selection and household validation. + "nsece_childcare_dependence.py", # Household dependence estimation. + "nsece_childcare_bridge.py", # Source measurement completion; no spine routing. "operator_boundary.py", # Raw-stage validator; no population treatment. "org_wages.py", "parity_reference.py", diff --git a/packages/microcosm-calibrate/src/microcosm/calibrate/geography_constants.py b/packages/microcosm-calibrate/src/microcosm/calibrate/geography_constants.py index c4a55de62..ac7566226 100644 --- a/packages/microcosm-calibrate/src/microcosm/calibrate/geography_constants.py +++ b/packages/microcosm-calibrate/src/microcosm/calibrate/geography_constants.py @@ -15,6 +15,7 @@ "US_STATE_FIPS_TO_POSTAL", "US_STATE_NUMERIC_FIPS_TO_POSTAL", "US_STATE_POSTAL_TO_NUMERIC_FIPS", + "US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION", ] @@ -97,3 +98,37 @@ US_STATE_POSTAL_TO_NUMERIC_FIPS: Mapping[str, int] = MappingProxyType( {postal: fips for fips, postal in US_STATE_NUMERIC_FIPS_TO_POSTAL.items()} ) + + +# Census region codes, https://www2.census.gov/geo/pdfs/maps-data/maps/reference/us_regdiv.pdf +_US_CENSUS_REGION_POSTAL_CODES = { + 1: ("CT", "ME", "MA", "NH", "RI", "VT", "NJ", "NY", "PA"), + 2: ("IN", "IL", "MI", "OH", "WI", "IA", "KS", "MN", "MO", "NE", "ND", "SD"), + 3: ( + "DE", + "DC", + "FL", + "GA", + "MD", + "NC", + "SC", + "VA", + "WV", + "AL", + "KY", + "MS", + "TN", + "AR", + "LA", + "OK", + "TX", + ), + 4: ("AZ", "CO", "ID", "MT", "NV", "NM", "UT", "WY", "AK", "CA", "HI", "OR", "WA"), +} +US_STATE_NUMERIC_FIPS_TO_CENSUS_REGION: Mapping[int, int] = MappingProxyType( + { + US_STATE_POSTAL_TO_NUMERIC_FIPS[postal]: region + for region, states in _US_CENSUS_REGION_POSTAL_CODES.items() + for postal in states + } +) diff --git a/tools/build_us_fiscal_refresh_release.py b/tools/build_us_fiscal_refresh_release.py index acb168644..2389807c5 100644 --- a/tools/build_us_fiscal_refresh_release.py +++ b/tools/build_us_fiscal_refresh_release.py @@ -962,6 +962,14 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: ) parser.add_argument("--out", type=Path, required=True) parser.add_argument("--release-id") + parser.add_argument("--childcare-attendance-household-tsv", type=Path) + parser.add_argument("--childcare-attendance-calendar-tsv", type=Path) + parser.add_argument("--childcare-attendance-asec-cache", type=Path) + parser.add_argument( + "--childcare-attendance-inherit-outside-domain-baseline", + action="store_true", + help="Retain the engine baseline outside modeled ages 0-12; does not assert older-child nonattendance", + ) parser.add_argument( "--incumbent-diagnostics", type=Path, @@ -1556,6 +1564,17 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: help="Minimum seconds between progress uploads to the staging repo.", ) args = parser.parse_args(argv) + if bool(args.childcare_attendance_household_tsv) != bool( + args.childcare_attendance_calendar_tsv + ): + parser.error("Both NSECE household and calendar TSV paths are required") + if ( + args.childcare_attendance_inherit_outside_domain_baseline + and not args.childcare_attendance_household_tsv + ): + parser.error( + "Outside-domain baseline policy requires an NSECE attendance build" + ) if args.congressional_district_vintage_crosswalk is None: # Every build compiles the same national + state + CD target surface, # translated through the canonical packaged vintage crosswalk unless @@ -9327,6 +9346,19 @@ def _main(argv: Sequence[str] | None = None) -> None: time_period=PERIOD, allow_existing_without_source=True, ) + if args.childcare_attendance_household_tsv is not None: + from microcosm.build.us_runtime.childcare_attendance_stage import ( + with_us_childcare_attendance_inputs, + ) + + base_frame = with_us_childcare_attendance_inputs( + base_frame, + household_tsv=args.childcare_attendance_household_tsv, + calendar_tsv=args.childcare_attendance_calendar_tsv, + asec_source_cache=args.childcare_attendance_asec_cache, + seed=args.seed, + inherit_outside_domain_baseline=args.childcare_attendance_inherit_outside_domain_baseline, + ) childcare_gate = us_childcare_signal_gate(base_frame) if not childcare_gate.passed: if telemetry is not None: @@ -11766,6 +11798,18 @@ def _main(argv: Sequence[str] | None = None) -> None: reviewed_exclusions=_reviewed_exclusions(active_aliases), ) coverage["fiscal_target_sources"] = _fiscal_target_source_provenance(target_specs) + attendance_receipts = { + key: json.loads(json.dumps(base_frame.metadata[key], default=dict)) + for key in ( + "childcare_attendance_stage", + "nsece_childcare_attendance", + "childcare_predictor_harmonization", + "childcare_outside_domain_baseline", + ) + if key in base_frame.metadata + } + if attendance_receipts: + coverage["childcare_attendance"] = attendance_receipts if congressional_district_vintage_crosswalk_metadata is not None: coverage["congressional_district_vintage_crosswalk"] = ( congressional_district_vintage_crosswalk_metadata diff --git a/tools/build_us_release_input_coverage_manifest.py b/tools/build_us_release_input_coverage_manifest.py index fbb5b03c0..b79cdd36f 100644 --- a/tools/build_us_release_input_coverage_manifest.py +++ b/tools/build_us_release_input_coverage_manifest.py @@ -39,6 +39,10 @@ import json from pathlib import Path +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) + US_PACKAGE_DIR = ( Path(__file__).resolve().parents[1] / "packages" @@ -67,6 +71,7 @@ # Schedule-D leg. These later inputs are hard requirements because the # shipped validation provisions must bind. POST_REFERENCE_ECPS_REQUIRED_INPUTS = ( + *US_CHILDCARE_ATTENDANCE_COLUMNS, "fsla_overtime_premium", "qualified_passenger_vehicle_loan_interest", "traditional_401k_contributions_desired", @@ -1464,6 +1469,10 @@ def build_manifest() -> dict: "gate fails until the asset stage is restored (Deliverable " "2). Currently absent — this is the intended red gate." ) + elif name in US_CHILDCARE_ATTENDANCE_COLUMNS: + column["note"] = ( + "Person-level NSECE attendance source extension (#915): requires persisted signal; missing inputs must not silently become engine defaults. The modeled age domain and outside-domain baseline policy are recorded in source coverage." + ) elif name in POST_REFERENCE_COLUMN_NOTES: column["note"] = POST_REFERENCE_COLUMN_NOTES[name] columns[name] = column @@ -1517,7 +1526,7 @@ def build_manifest() -> dict: "qualified_passenger_vehicle_loan_interest, five desired " "retirement-contribution inputs, " "meets_ssi_disability_criteria required by shipped validation " - "probes, and the #282 Schedule-D capital-gain-distributions " + "probes, the three NSECE child attendance inputs (#915), and the #282 Schedule-D capital-gain-distributions " "route leg schedule_d_capital_gain_distributions " "(PolicyEngine/microcosm#462). " "status='reviewed_exclusion' for ecps_parity_known_gaps.json entries " diff --git a/tools/generate_us_bundle_from_constants.py b/tools/generate_us_bundle_from_constants.py index 465bd4a4f..e30f3ab27 100644 --- a/tools/generate_us_bundle_from_constants.py +++ b/tools/generate_us_bundle_from_constants.py @@ -232,9 +232,11 @@ def country_manifest() -> dict[str, object]: } for kind in DOMAIN_KINDS ] + # The local source extension uses the existing SourceStageSpec compatibility + # interpreter; the three generation-0 projections remain byte-frozen. legacy_rows = [ {"path": path, "kind": "legacy_json", "schema_id": "legacy_json"} - for path in LEGACY_RESOURCE_PATHS + for path in (*LEGACY_RESOURCE_PATHS, "childcare_attendance_source.json") ] return {"schema_version": 1, "country": "us", "resources": typed_rows + legacy_rows} diff --git a/tools/prepare_us_childcare_attendance.py b/tools/prepare_us_childcare_attendance.py index b14bc6bbb..d9e4507f3 100644 --- a/tools/prepare_us_childcare_attendance.py +++ b/tools/prepare_us_childcare_attendance.py @@ -20,12 +20,32 @@ write_frame_checkpoint, ) from microcosm.build.us_runtime import childcare_attendance, nsece_childcare +from microcosm.build.us_runtime.childcare_attendance_stage import ( + export_native_childcare_candidate, + inherit_outside_domain_attendance_baseline, + with_us_childcare_attendance_inputs, +) +from microcosm.build.us_runtime.childcare_population import ( + harmonize_asec_childcare_predictors, +) +from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, NSECE_CHILDCARE_MATCH_COLUMNS, load_nsece_childcare, nsece_childcare_validation_report, with_us_nsece_childcare_attendance, ) +from microcosm.build.us_runtime.nsece_childcare_assessment import ( + assess_noncalendar_bridge, + assess_nsece_childcare, +) +from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_dependence import ( + fit_nsece_sibling_dependence, +) from microcosm.frame import Frame @@ -35,17 +55,87 @@ def main() -> None: parser.add_argument("--calendar-tsv", type=Path, required=True) parser.add_argument("--report", type=Path, required=True) parser.add_argument("--seed", type=int, default=915) + parser.add_argument( + "--model-sibling-dependence", + action="store_true", + help="Fit measured household dependence for shared-rank schedule draws", + ) + parser.add_argument( + "--bridge-noncalendar", + action="store_true", + help="Use measured regular hours to impute missing days and irregular care", + ) + parser.add_argument( + "--extended-assessment", + action="store_true", + help="Run household cross-validation, instrument transport and masked-calendar checks", + ) parser.add_argument( "--match-columns", nargs="+", default=list(NSECE_CHILDCARE_MATCH_COLUMNS) ) - parser.add_argument("--input-checkpoint", type=Path) + inputs = parser.add_mutually_exclusive_group() + inputs.add_argument("--input-checkpoint", type=Path) + inputs.add_argument("--asec-population-h5", type=Path) + parser.add_argument( + "--asec-source-cache", + type=Path, + help="Verified Census pppub23/24/25.csv files for omitted PTOTVAL", + ) + parser.add_argument( + "--population-sha256", help="Required exact hash for --asec-population-h5" + ) + parser.add_argument( + "--sparse-cell-fallback", + action="store_true", + help="Explicitly use age/parent-work then age-only donor matches", + ) parser.add_argument("--output-checkpoint", type=Path) + parser.add_argument("--output-native-h5", type=Path) + parser.add_argument( + "--inherit-outside-domain-baseline", + action="store_true", + help="Explicitly retain baseline outside ages 0-12 for export; not observed nonattendance", + ) + parser.add_argument( + "--production-stage", + action="store_true", + help="Run the same complete attendance stage used by the fiscal refresh builder", + ) args = parser.parse_args() - if bool(args.input_checkpoint) != bool(args.output_checkpoint): + if args.production_stage: + if not args.asec_population_h5: + parser.error( + "The production-stage qualification requires a native ASEC parent" + ) + args.bridge_noncalendar = args.model_sibling_dependence = ( + args.sparse_cell_fallback + ) = True + input_path = args.input_checkpoint or args.asec_population_h5 + if bool(input_path) != bool(args.output_checkpoint): parser.error( - "--input-checkpoint and --output-checkpoint must be supplied together" + "A population input and --output-checkpoint must be supplied together" ) - outputs = [p for p in (args.report, args.output_checkpoint) if p is not None] + if args.asec_population_h5 and ( + not args.population_sha256 + or _sha256(args.asec_population_h5) != args.population_sha256 + ): + parser.error("The ASEC population must match --population-sha256") + if ( + args.sparse_cell_fallback + and tuple(args.match_columns) != NSECE_CHILDCARE_MATCH_COLUMNS + ): + parser.error("The reviewed fallback requires the canonical matching fields") + if args.output_native_h5 and ( + not args.asec_population_h5 or not args.output_checkpoint + ): + parser.error( + "Native output requires an exact ASEC population parent and checkpoint output" + ) + outputs = [ + p + for p in (args.report, args.output_checkpoint, args.output_native_h5) + if p is not None + ] if any(p.exists() for p in outputs): parser.error( "Output paths must be new; existing artifacts will not be overwritten" @@ -56,10 +146,28 @@ def main() -> None: report = nsece_childcare_validation_report( source, seed=args.seed, match_columns=tuple(args.match_columns) ) + sibling_fit = ( + fit_nsece_sibling_dependence(source.children) + if args.model_sibling_dependence + else {"rho": 0.0} + ) + report["sibling_dependence"] = sibling_fit + if args.extended_assessment: + report["selection_and_cross_validation"] = assess_nsece_childcare(source) + report["masked_calendar_validation"] = assess_noncalendar_bridge(source) + if args.bridge_noncalendar: + source = bridge_nsece_noncalendar_attendance(source, seed=args.seed) + report["noncalendar_bridge"] = source.source_receipt["noncalendar_bridge"] report["candidate_frame_written"] = False report["environment"] = { package: version(package) - for package in ("numpy", "pandas", "microcosm-build", "microcosm-frame") + for package in ( + "numpy", + "pandas", + "microcosm-build", + "microcosm-frame", + "policyengine-us", + ) } report["environment"]["python"] = platform.python_version() report["code_sha256"] = { @@ -68,11 +176,32 @@ def main() -> None: Path(__file__), Path(childcare_attendance.__file__), Path(nsece_childcare.__file__), + *Path(childcare_attendance.__file__).parent.glob("*childcare*.py"), + Path(childcare_attendance.__file__).parent.parent + / "us" + / "childcare_attendance_source.json", + Path(childcare_attendance.__file__).parent + / "education_assistance_source.py", + Path(__file__).resolve().parents[1] + / "packages/microcosm-calibrate/src/microcosm/calibrate/geography_constants.py", + Path(__file__).resolve().parents[1] / "uv.lock", ) } try: - if args.input_checkpoint is not None: - original = load_frame_checkpoint(args.input_checkpoint) + if input_path is not None: + if args.asec_population_h5: + from microcosm.build.frame_checkpoint import LoadedFrameCheckpoint + + frame = load_legacy_calibrated_us_h5(input_path) + if not args.production_stage: + frame = harmonize_asec_childcare_predictors( + frame, source_cache=args.asec_source_cache + ) + original = LoadedFrameCheckpoint( + frame, {"parent_population_sha256": args.population_sha256} + ) + else: + original = load_frame_checkpoint(input_path) # The checkpoint protocol deliberately stores Frame receipts as # external metadata; restore them explicitly rather than dropping # the parent build's source/ownership evidence. @@ -91,12 +220,28 @@ def main() -> None: **original.metadata.get("frame_metadata", {}), }, ) - candidate = with_us_nsece_childcare_attendance( - parent, - source, - seed=args.seed, - match_columns=tuple(args.match_columns), - ) + if args.production_stage: + candidate = with_us_childcare_attendance_inputs( + parent, + household_tsv=args.household_tsv, + calendar_tsv=args.calendar_tsv, + asec_source_cache=args.asec_source_cache, + seed=args.seed, + inherit_outside_domain_baseline=args.inherit_outside_domain_baseline, + ) + else: + candidate = with_us_nsece_childcare_attendance( + parent, + source, + seed=args.seed, + match_columns=tuple(args.match_columns), + fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS + if args.sparse_cell_fallback + else (), + sibling_dependence=sibling_fit["rho"], + ) + if args.inherit_outside_domain_baseline: + candidate = inherit_outside_domain_attendance_baseline(candidate) write_frame_checkpoint( args.output_checkpoint, candidate, @@ -104,13 +249,25 @@ def main() -> None: "artifact_kind": "nsece_childcare_candidate", "childcare_candidate_only": True, "parent_checkpoint_metadata": original.metadata, - "parent_checkpoint_sha256": _sha256(args.input_checkpoint), + "parent_checkpoint_sha256": _sha256(input_path), "frame_metadata": json.loads( json.dumps(candidate.metadata, default=dict) ), }, ) report["candidate_frame_written"] = True + report["candidate_checkpoint_sha256"] = _sha256(args.output_checkpoint) + report["parent_population_sha256"] = _sha256(input_path) + report["production_stage_executed"] = args.production_stage + report["candidate_receipts"] = json.loads( + json.dumps(candidate.metadata, default=dict) + ) + if args.output_native_h5: + export_native_childcare_candidate( + args.asec_population_h5, candidate, args.output_native_h5 + ) + report["native_candidate_written"] = True + report["native_candidate_sha256"] = _sha256(args.output_native_h5) except Exception as exc: report["candidate_error"] = f"{type(exc).__name__}: {exc}" raise diff --git a/tools/validate_us_childcare_population.py b/tools/validate_us_childcare_population.py new file mode 100644 index 000000000..e1f828b42 --- /dev/null +++ b/tools/validate_us_childcare_population.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Compare a local attendance candidate with its exact parent under each state's model. + +Only finite, imputed child inputs change in this counterfactual. Unresolved +out-of-domain inputs retain their baseline engine behavior and are counted, +not certified as observed zeros. Results are potential modeled benefits, not +CCDF caseload or spending estimates. No microdata are published by this tool. +""" + +from __future__ import annotations + +import argparse +import gc +import hashlib +import json +from importlib.metadata import version +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.frame_checkpoint import load_frame_checkpoint +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 +from microcosm.calibrate.geography_constants import US_STATE_NUMERIC_FIPS_TO_POSTAL + + +def _sha256(path: Path) -> str: + with path.open("rb") as stream: + return hashlib.file_digest(stream, "sha256").hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--parent-h5", type=Path, required=True) + parser.add_argument("--parent-sha256", required=True) + parser.add_argument("--candidate-checkpoint", type=Path, required=True) + parser.add_argument("--year", type=int, default=2026) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("The report path must be new") + with args.parent_h5.open("rb") as stream: + parent_hash = hashlib.file_digest(stream, "sha256").hexdigest() + if parent_hash != args.parent_sha256: + parser.error("Parent population hash mismatch") + parent = load_legacy_calibrated_us_h5(args.parent_h5) + stored = load_frame_checkpoint(args.candidate_checkpoint) + candidate = stored.frame + for entity in parent.entities: + pd.testing.assert_frame_equal( + parent.table(entity), candidate.table(entity)[parent.table(entity).columns] + ) + for entity in parent.weighted_entities: + np.testing.assert_array_equal( + parent.weights_for(entity).values, candidate.weights_for(entity).values + ) + p = candidate.table("person") + attendance = p[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].to_numpy(dtype=float) + resolved = np.isfinite(attendance).all(axis=1) + young = p.age.between(0, 12).to_numpy() + if not resolved[young].all(): + raise ValueError("Candidate leaves under-13 attendance unresolved") + hh = parent.table("household").copy() + hh["household_weight"] = parent.weights_for("household").values + person_weights = p.person_household_id.map( + hh.set_index("household_id").household_weight + ).to_numpy() + days = attendance[:, 1] + report = { + "parent_sha256": parent_hash, + "candidate_checkpoint_sha256": _sha256(args.candidate_checkpoint), + "validation_code_sha256": _sha256(Path(__file__)), + "engine_version": version("policyengine-us"), + "policy_year": args.year, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "people": len(p), + "households": len(hh), + "under13_children": int(young.sum()), + "outside_source_domain_people": int((~young).sum()), + "outside_source_domain_age13_17_with_disability": int( + (p.age.between(13, 17) & p.is_disabled).sum() + ), + "outside_domain_baseline_receipt": stored.metadata.get( + "frame_metadata", {} + ).get("childcare_outside_domain_baseline", {}), + "unresolved_people_retaining_baseline": int((~resolved).sum()), + "unresolved_age13_17_with_disability": int( + (~resolved & p.age.between(13, 17) & p.is_disabled).sum() + ), + "weighted_under13_attendance_share": float( + np.average(days[young] > 0, weights=person_weights[young]) + ), + "weighted_under13_days_per_week": float( + np.average(days[young], weights=person_weights[young]) + ), + "weighted_under13_hours_per_week": float( + np.average( + days[young] * attendance[young, 2], weights=person_weights[young] + ) + ), + "candidate_receipt": stored.metadata.get("frame_metadata", {}), + "states": [], + "production_ready": False, + "interpretation": "attendance-only counterfactual; provider, activity, expenses and take-up inputs remain as in parent", + } + if "childcare_attendance_match_level" in p: + report["matching_levels"] = ( + p.loc[young, "childcare_attendance_match_level"].value_counts().to_dict() + ) + report["population_attendance_by_group"] = [] + for grouping in ("age", "region", "parent_work_status", "income_band"): + for label, indices in p.loc[young].groupby(grouping).groups.items(): + positions = p.index.get_indexer(indices) + w = person_weights[positions] + d = attendance[positions, 1] + report["population_attendance_by_group"].append( + { + "grouping": grouping, + "group": str(label), + "children": len(indices), + "weighted_children": float(w.sum()), + "attendance_share": float(np.average(d > 0, weights=w)), + "days_per_week": float(np.average(d, weights=w)), + "hours_per_week": float( + np.average(d * attendance[positions, 2], weights=w) + ), + } + ) + from policyengine_us import Microsimulation + from policyengine_us.data import USSingleYearDataset + + try: + for state_fips, households in hh.groupby("state_fips", sort=True): + state = US_STATE_NUMERIC_FIPS_TO_POSTAL[int(state_fips)] + rows = p.person_household_id.isin(households.household_id) + people = parent.table("person").loc[rows].copy() + tables = {"person": people, "household": households} + for entity in parent.schema.group_entities: + if entity != "household": + table = parent.table(entity) + tables[entity] = table.loc[ + table[f"{entity}_id"].isin(people[f"person_{entity}_id"]) + ] + result = {"state": state, "sample_households": len(households)} + for name in ("baseline", "candidate"): + sim = Microsimulation( + dataset=USSingleYearDataset(**tables, time_period=args.year) + ) + if name == "candidate": + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + values = np.asarray(sim.calculate(column, args.year)).copy() + known = resolved[rows] + values[known] = p.loc[rows, column].to_numpy()[known] + sim.set_input(column, args.year, values) + variable = f"{state.lower()}_child_care_subsidies" + if variable not in sim.tax_benefit_system.variables: + raise ValueError(f"The pinned engine has no {variable}") + definition = sim.tax_benefit_system.variables[variable] + entity = definition.entity.key + amount = np.asarray(sim.calculate(variable, args.year), dtype=float) + weights = np.asarray( + sim.calculate(f"{entity}_weight", args.year), dtype=float + ) + if not np.isfinite(amount).all(): + raise ValueError(f"Nonfinite state benefit: {state}/{name}") + result[name] = { + "variable": variable, + "entity": entity, + "positive_sample_units": int((amount > 0).sum()), + "weighted_positive_units": float(weights[amount > 0].sum()), + "annual_modeled_benefits": float(amount @ weights), + } + del sim + gc.collect() + report["states"].append(result) + print( + f"{state}: {result['baseline']['positive_sample_units']} -> {result['candidate']['positive_sample_units']} positive units", + flush=True, + ) + report["summary"] = { + "states_evaluated": len(report["states"]), + "all_zero_states": { + arm: [ + row["state"] + for row in report["states"] + if row[arm]["positive_sample_units"] == 0 + ] + for arm in ("baseline", "candidate") + }, + "annual_potential_modeled_benefits": { + arm: sum( + row[arm]["annual_modeled_benefits"] for row in report["states"] + ) + for arm in ("baseline", "candidate") + }, + } + except Exception as exc: + report["error"] = f"{type(exc).__name__}: {exc}" + raise + finally: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text( + json.dumps(report, indent=2, allow_nan=False, default=dict) + "\n" + ) + + +if __name__ == "__main__": + main() From 3fe3e6846fc7b315be11a40628cde3e42e8044ee Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sun, 13 Sep 2026 12:06:57 -0400 Subject: [PATCH 05/16] Fix serializer registration and US spec coverage attestations --- docs/evidence/spec-engine/us-f0-coverage.json | 20 ++++---- experiments/us-childcare-attendance/README.md | 25 ++++++++++ .../ci-export-verification.json | 9 ++++ .../build/frame_serializer_registry.py | 11 ++++ .../build/spec_engine/field_usage.py | 9 ++-- .../us_runtime/childcare_attendance_stage.py | 29 +++++++---- .../tests/test_frame_serializer_registry.py | 29 +++++++++-- .../tests/test_spec_engine_coverage_tool.py | 10 ++-- .../tests/test_spec_engine_field_usage.py | 50 ++++++++++++------- .../tests/test_us_multispine_pool_tool.py | 2 +- tools/spec_engine_coverage.py | 2 +- 11 files changed, 145 insertions(+), 51 deletions(-) create mode 100644 experiments/us-childcare-attendance/ci-export-verification.json diff --git a/docs/evidence/spec-engine/us-f0-coverage.json b/docs/evidence/spec-engine/us-f0-coverage.json index a759198bc..ab491ed77 100644 --- a/docs/evidence/spec-engine/us-f0-coverage.json +++ b/docs/evidence/spec-engine/us-f0-coverage.json @@ -6,7 +6,7 @@ "country": "us", "documentation_sha256": "4b39450dbdb8dafb83c3b627123b8026c6f82c660b66fe76f341a67c4f37c77b", "field_usage": { - "authored_normative_field_count": 32384, + "authored_normative_field_count": 32387, "claim_count": 49, "claims": [ { @@ -16,8 +16,8 @@ "legacy_sinks": [], "mode": "front_end_validation", "pointer_class": "all", - "pointer_count": 98, - "pointer_sha256": "cbbda6d2d245f04325c0b5a7b986cb71d24d6e3c81a7a3af1544de7f75be2a1f", + "pointer_count": 101, + "pointer_sha256": "be7cb568758bd3374612d2bcc37ea96a8269ffeac53fe0d10dc62195471b0d2e", "rationale": null, "relative_sink_prefix": null, "source_prefix": "/authored/country_package.json", @@ -772,20 +772,20 @@ "verifier": "vintages" } ], - "configuration_field_count": 42156, - "consumed_field_count": 42156, + "configuration_field_count": 42159, + "consumed_field_count": 42159, "generation0_effect_counts": { "legacy_behavior": 38476, - "no_generation0_effect": 3680 + "no_generation0_effect": 3683 }, "mode_counts": { "compiler_semantic": 27717, - "front_end_validation": 348, + "front_end_validation": 351, "identity_only": 103, "legacy_behavior": 13988 }, "multiple_primary_use_field_count": 0, - "pointer_inventory_sha256": "2c0423a08dc16bf5f142134804a1a6545f887e32ca5c991c856a2f8bcfaea0f0", + "pointer_inventory_sha256": "199823f8e1bc6cc7f966e3bb2afc131a8a992a25183282994f87801c7020a7d9", "resolved_binding_field_count": 9772, "unused_field_count": 0 }, @@ -2599,7 +2599,7 @@ "country": "us", "schema_id": "country_spec", "schema_version": 1, - "spec_sha256": "1eeca53aa80da949a292fbd8cb0afefde95c68888ed35f3f477f3c962e6bc644" + "spec_sha256": "98bf29eb15c96d4c4e8de1eafc5ff530df5f7de82375bd8c6338e8e9da569435" } }, "report_schema_version": 3, @@ -2609,7 +2609,7 @@ "country": "us", "schema_id": "country_spec", "schema_version": 1, - "spec_sha256": "1eeca53aa80da949a292fbd8cb0afefde95c68888ed35f3f477f3c962e6bc644" + "spec_sha256": "98bf29eb15c96d4c4e8de1eafc5ff530df5f7de82375bd8c6338e8e9da569435" }, "status": "pass" } diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index 9159609ba..3cdfeae4f 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -107,3 +107,28 @@ inventory, generated bundle validation, and diff whitespace checks passed. The real production stage completed on the pinned parent, and native export reloaded successfully with original data, weights, and period preserved. Full GitHub CI runs separately on the submitted commit. + + +## CI integration correction — 2026-09-13 + +The initial commit's full CI exposed three omissions outside the local targeted +selection: the new native writer was absent from the serializer registry, the +new source descriptor's three manifest fields were missing from field-ledger +pins and the generated coverage report, and a pool-tool test retained the old +US spec hash. The same failures repeated across Python versions and test lanes. + +The writer now uses the shared nullable-boolean table boundary and has registry +round-trip coverage for mixed and all-missing Boolean columns. The configuration +ledger retains exact counts and pointer hashes with explicit validation claims +for the new descriptor; its semantic/missing-sink checks remain enforced. + +The [full-population export verification](ci-export-verification.json) reran the +corrected writer on the qualified checkpoint. All 166,321 people's entity-table +values, dtypes, weights, and the time period match the original qualified native +candidate exactly. Attendance estimates and the 31-to-3 state comparison are +unchanged; artifact bytes have their own new hash. + +The correction passed 579 tests across the serializer/source, field-ledger, +coverage-report, pool-spec identity, and architecture regression runs. The +coverage generator reports 42,159/42,159 fields and 41/41 inventory checks. +Repository lint, CI test inventory, and the build wheel also passed locally. diff --git a/experiments/us-childcare-attendance/ci-export-verification.json b/experiments/us-childcare-attendance/ci-export-verification.json new file mode 100644 index 000000000..319bd2f5f --- /dev/null +++ b/experiments/us-childcare-attendance/ci-export-verification.json @@ -0,0 +1,9 @@ +{ + "previous_candidate_sha256": "a2d0ed98081b3707727eddb3d90e007117aa1da50e75c83a1a33fe381f41bcd2", + "updated_candidate_sha256": "7e4231f15d48ec060a07616068b5cfd03d8e4e45c0ef089dfacaa01d73dcc44f", + "checkpoint_sha256": "882c2ae6b683e2a04ce6c94f5f694f70750a8c2876e8108e3715a7f7095d1f66", + "export_code_sha256": "b8dc8009aa87e31e569261d1f1f5efdfb5c59b3345c62360ef9303d496f3963c", + "all_entity_values_dtypes_weights_period_equal": true, + "people": 166321, + "interpretation": "Serializer registry/dtype fix only; qualified attendance values unchanged." +} diff --git a/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py b/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py index bb8a49fe0..c81e009c1 100644 --- a/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py +++ b/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py @@ -47,6 +47,17 @@ class HdfWriteExclusion: FRAME_TABLE_SERIALIZERS = ( + FrameSerializerSpec( + serializer_id="nsece_childcare_native_candidate", + writer=HdfWriteSite( + "packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py", + "_write_childcare_candidate_person_table", + ), + backend="pandas.HDFStore table", + routes=("NSECE attendance native population candidate",), + version_owner="NSECE native candidate payload contract", + nullable_boolean_storage="numpy_bool_or_object_pd_na_v1", + ), FrameSerializerSpec( serializer_id="frame_checkpoint", writer=HdfWriteSite( diff --git a/packages/microcosm-build/src/microcosm/build/spec_engine/field_usage.py b/packages/microcosm-build/src/microcosm/build/spec_engine/field_usage.py index 107265aeb..ba8661556 100644 --- a/packages/microcosm-build/src/microcosm/build/spec_engine/field_usage.py +++ b/packages/microcosm-build/src/microcosm/build/spec_engine/field_usage.py @@ -26,9 +26,9 @@ ) from .schemas import load_schema_registry -EXPECTED_AUTHORED_FIELD_COUNT = 32_384 +EXPECTED_AUTHORED_FIELD_COUNT = 32_387 EXPECTED_RESOLVED_BINDING_FIELD_COUNT = 9_772 -EXPECTED_CONFIGURATION_FIELD_COUNT = 42_156 +EXPECTED_CONFIGURATION_FIELD_COUNT = 42_159 class FieldUsageError(AssertionError): @@ -372,9 +372,10 @@ def _path_inventory(rows: Sequence[tuple[str, object]]) -> tuple[int, str]: 5, "64182b6be1ea6d95bff345b30a2aa046b6fa7e8ee61a282b225dfa49c28fbfdc", ), + # The NSECE source-extension descriptor adds kind/path/schema_id leaves. "country_manifest": ( - 98, - "cbbda6d2d245f04325c0b5a7b986cb71d24d6e3c81a7a3af1544de7f75be2a1f", + 101, + "be7cb568758bd3374612d2bcc37ea96a8269ffeac53fe0d10dc62195471b0d2e", ), "generated_authorities": ( 8_606, diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py index 434135e20..59b01f905 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py @@ -37,7 +37,7 @@ from microcosm.build.us_runtime.nsece_childcare_dependence import ( fit_nsece_sibling_dependence, ) -from microcosm.frame import Frame +from microcosm.frame import Frame, put_frame_table def inherit_outside_domain_attendance_baseline(frame: Frame) -> Frame: @@ -198,14 +198,7 @@ def export_native_childcare_candidate( raise ValueError("Native childcare temporary path already exists.") try: shutil.copyfile(parent_path, temporary) - with pd.HDFStore(temporary, mode="a") as store: - store.put("person", people, format="table", data_columns=True) - store.put( - "_childcare_attendance_receipt", - pd.Series( - [json.dumps(candidate.metadata, default=dict, allow_nan=False)] - ), - ) + _write_childcare_candidate_person_table(temporary, people, candidate.metadata) loaded = USSingleYearDataset(file_path=str(temporary)) pd.testing.assert_frame_equal(loaded.person, people) for entity in candidate.schema.group_entities: @@ -218,3 +211,21 @@ def export_native_childcare_candidate( finally: temporary.unlink(missing_ok=True) return output_path + + +def _write_childcare_candidate_person_table( + path: Path, people: pd.DataFrame, receipt: dict +) -> None: + """Replace the prepared person table through the shared dtype boundary. + + The caller owns the temporary copy and verifies the native dataset reload. + Keep nullable booleans and their masks intact if the person table has them. + """ + with pd.HDFStore(path, mode="a") as store: + put_frame_table( + store, "person", people, preferred_format="table", data_columns=True + ) + store.put( + "_childcare_attendance_receipt", + pd.Series([json.dumps(receipt, default=dict, allow_nan=False)]), + ) diff --git a/packages/microcosm-build/tests/test_frame_serializer_registry.py b/packages/microcosm-build/tests/test_frame_serializer_registry.py index 11edf2aeb..07bb7bb1e 100644 --- a/packages/microcosm-build/tests/test_frame_serializer_registry.py +++ b/packages/microcosm-build/tests/test_frame_serializer_registry.py @@ -350,7 +350,28 @@ def _round_trip_fiscal_checkpoint( ) +def _round_trip_childcare_candidate( + tmp_path: Path, nullable_case: str +) -> BooleanRoundTrip: + pytest.importorskip("tables") + from microcosm.build.us_runtime.childcare_attendance_stage import ( + _write_childcare_candidate_person_table, + ) + + source = _dtype_family_table(nullable_case) + before = source.copy(deep=True) + path = tmp_path / "childcare-candidate.h5" + _write_childcare_candidate_person_table(path, source, {"test_receipt": "preserved"}) + with pd.HDFStore(path, mode="r") as store: + loaded = read_frame_table(store, "person") + assert json.loads(store["_childcare_attendance_receipt"].iloc[0]) == { + "test_receipt": "preserved" + } + return _semantic_observation(source, before, loaded) + + ROUND_TRIP_ADAPTERS: dict[str, RoundTripAdapter] = { + "nsece_childcare_native_candidate": _round_trip_childcare_candidate, "frame_checkpoint": _round_trip_frame_checkpoint, "nullable_us_h5": _round_trip_nullable_us_h5, "uk_single_year_h5": _round_trip_uk_single_year, @@ -428,10 +449,10 @@ def test_registry_classifies_every_writable_production_hdf_site() -> None: assert _discover_writable_hdf_sites() == classified -def test_registry_has_exactly_eight_unique_frame_table_serializers() -> None: - assert len(FRAME_TABLE_SERIALIZERS) == 8 - assert len({spec.serializer_id for spec in FRAME_TABLE_SERIALIZERS}) == 8 - assert len({spec.writer.key for spec in FRAME_TABLE_SERIALIZERS}) == 8 +def test_registry_has_exactly_nine_unique_frame_table_serializers() -> None: + assert len(FRAME_TABLE_SERIALIZERS) == 9 + assert len({spec.serializer_id for spec in FRAME_TABLE_SERIALIZERS}) == 9 + assert len({spec.writer.key for spec in FRAME_TABLE_SERIALIZERS}) == 9 def test_round_trip_adapter_registry_exactly_matches_serializer_registry() -> None: diff --git a/packages/microcosm-build/tests/test_spec_engine_coverage_tool.py b/packages/microcosm-build/tests/test_spec_engine_coverage_tool.py index 9fb08bea1..3d4765870 100644 --- a/packages/microcosm-build/tests/test_spec_engine_coverage_tool.py +++ b/packages/microcosm-build/tests/test_spec_engine_coverage_tool.py @@ -88,22 +88,22 @@ def test_us_coverage_is_exact_complete_and_honest( assert_coverage_complete(coverage_report) assert coverage_report["status"] == "pass" fields = coverage_report["field_usage"] - assert fields["configuration_field_count"] == 42_156 - assert fields["authored_normative_field_count"] == 32_384 + assert fields["configuration_field_count"] == 42_159 + assert fields["authored_normative_field_count"] == 32_387 assert fields["resolved_binding_field_count"] == 9_772 - assert fields["consumed_field_count"] == 42_156 + assert fields["consumed_field_count"] == 42_159 assert fields["unused_field_count"] == 0 assert fields["multiple_primary_use_field_count"] == 0 assert fields["claim_count"] == 49 assert fields["mode_counts"] == { "legacy_behavior": 13_988, "compiler_semantic": 27_717, - "front_end_validation": 348, + "front_end_validation": 351, "identity_only": 103, } assert fields["generation0_effect_counts"] == { "legacy_behavior": 38_476, - "no_generation0_effect": 3_680, + "no_generation0_effect": 3_683, } inventory = coverage_report["inventory_coverage"] diff --git a/packages/microcosm-build/tests/test_spec_engine_field_usage.py b/packages/microcosm-build/tests/test_spec_engine_field_usage.py index 1146f6c04..c380f7b62 100644 --- a/packages/microcosm-build/tests/test_spec_engine_field_usage.py +++ b/packages/microcosm-build/tests/test_spec_engine_field_usage.py @@ -17,6 +17,7 @@ Generation0Effect, UsageMode, build_field_usage_ledger, + configuration_sources, default_usage_claims, ) from microcosm.build.spec_engine.legacy_adapter import ( @@ -92,22 +93,22 @@ def _mutated_bundle( def test_exact_complete_ledger_has_one_primary_mode_per_pointer(field_ledger) -> None: - assert len(field_ledger.fields) == EXPECTED_CONFIGURATION_FIELD_COUNT == 42_156 + assert len(field_ledger.fields) == EXPECTED_CONFIGURATION_FIELD_COUNT == 42_159 assert field_ledger.source_counts == { - "authored": 32_384, + "authored": 32_387, "resolved_bindings": 9_772, } assert field_ledger.mode_counts == { "legacy_behavior": 13_988, "compiler_semantic": 27_717, - "front_end_validation": 348, + "front_end_validation": 351, "identity_only": 103, } assert field_ledger.generation0_effect_counts == { "legacy_behavior": 38_476, - "no_generation0_effect": 3_680, + "no_generation0_effect": 3_683, } - assert len({field.pointer for field in field_ledger.fields}) == 42_156 + assert len({field.pointer for field in field_ledger.fields}) == 42_159 def test_eligibility_concepts_are_validation_not_generation0_behavior( @@ -162,15 +163,11 @@ def test_spine_assembly_mass_share_fields_name_exact_adapter_sinks( ) -> None: for channel in ("acs", "asec"): field = field_ledger.field( - "/authored/spec~1spine.yaml/assembly/household_mass_shares/" - f"{channel}" + f"/authored/spec~1spine.yaml/assembly/household_mass_shares/{channel}" ) assert field.mode is UsageMode.LEGACY_BEHAVIOR assert field.generation0_effect is Generation0Effect.LEGACY_BEHAVIOR - assert ( - f"/spine_assembly/household_mass_shares/{channel}" - in field.sink_pointers - ) + assert f"/spine_assembly/household_mass_shares/{channel}" in field.sink_pointers def test_copied_surfaces_cannot_rescue_a_missing_calibration_sink( @@ -358,14 +355,10 @@ def mutate(document: dict[str, object]) -> None: ledger = build_field_usage_ledger(mutated, legacy_payload=mutated_legacy) for channel in ("acs", "asec"): field = ledger.field( - "/authored/spec~1spine.yaml/assembly/household_mass_shares/" - f"{channel}" + f"/authored/spec~1spine.yaml/assembly/household_mass_shares/{channel}" ) assert field.claim_id == "spine_assembly_household_mass_shares" - assert ( - f"/spine_assembly/household_mass_shares/{channel}" - in field.sink_pointers - ) + assert f"/spine_assembly/household_mass_shares/{channel}" in field.sink_pointers def test_geography_declaration_mutation_changes_checkpoint_identity( @@ -441,3 +434,26 @@ def mutate(document: dict[str, object]) -> None: ).field("/authored/spec~1sources.yaml/sources/7/sha256") assert field.claim_id == "source_geography_identity" assert field.mode is UsageMode.LEGACY_BEHAVIOR + + +def test_nsece_source_descriptor_has_explicit_manifest_validation_claim( + resolved_us, field_ledger +): + manifest = configuration_sources(resolved_us)["authored"]["country_package.json"] + index, descriptor = next( + (index, descriptor) + for index, descriptor in enumerate(manifest["resources"]) + if descriptor["path"] == "childcare_attendance_source.json" + ) + assert descriptor == { + "path": "childcare_attendance_source.json", + "kind": "legacy_json", + "schema_id": "legacy_json", + } + for name in descriptor: + field = field_ledger.field( + f"/authored/country_package.json/resources/{index}/{name}" + ) + assert field.claim_id == "country_manifest" + assert field.mode is UsageMode.FRONT_END_VALIDATION + assert field.generation0_effect is Generation0Effect.NO_GENERATION0_EFFECT diff --git a/packages/microcosm-build/tests/test_us_multispine_pool_tool.py b/packages/microcosm-build/tests/test_us_multispine_pool_tool.py index 683d496d4..641620f49 100644 --- a/packages/microcosm-build/tests/test_us_multispine_pool_tool.py +++ b/packages/microcosm-build/tests/test_us_multispine_pool_tool.py @@ -2504,7 +2504,7 @@ def capture_equality(expected: object, actual: object) -> None: "country": "us", "schema_id": "country_spec", "schema_version": 1, - "spec_sha256": "1eeca53aa80da949a292fbd8cb0afefde95c68888ed35f3f477f3c962e6bc644", + "spec_sha256": "98bf29eb15c96d4c4e8de1eafc5ff530df5f7de82375bd8c6338e8e9da569435", }, } diff --git a/tools/spec_engine_coverage.py b/tools/spec_engine_coverage.py index e8cd35660..0d2e7cecd 100644 --- a/tools/spec_engine_coverage.py +++ b/tools/spec_engine_coverage.py @@ -41,7 +41,7 @@ REPORT_SCHEMA_VERSION = 3 EXPECTED_POINTER_INVENTORY_SHA256 = ( - "2c0423a08dc16bf5f142134804a1a6545f887e32ca5c991c856a2f8bcfaea0f0" + "199823f8e1bc6cc7f966e3bb2afc131a8a992a25183282994f87801c7020a7d9" ) DEFAULT_REPORT_PATH = ( Path(__file__).resolve().parents[1] From a32ec066d7fc43cc1c931d20eb611985bb74ec56 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Tue, 15 Sep 2026 16:06:55 -0400 Subject: [PATCH 06/16] Bind childcare attendance provenance and expand validation --- docs/us-childcare-attendance.md | 56 +- experiments/us-childcare-attendance/README.md | 105 +- .../review-artifact-verification.json | 47 + .../review-source-stage-validation.json | 1921 ++++++++++ .../review-transport-sensitivity.json | 3214 +++++++++++++++++ .../review-validation-criteria.txt | 5 + .../build/us_runtime/childcare_attendance.py | 9 + .../childcare_attendance_receipt.py | 232 ++ .../us_runtime/childcare_attendance_stage.py | 156 +- .../build/us_runtime/childcare_population.py | 25 +- .../build/us_runtime/childcare_sensitivity.py | 108 + .../src/microcosm/build/us_runtime/h5_io.py | 6 + .../build/us_runtime/l0_refit_export.py | 6 + .../build/us_runtime/nsece_childcare.py | 69 +- .../nsece_childcare_sibling_validation.py | 285 ++ .../us_runtime/release_input_coverage.py | 23 +- .../tests/test_us_fiscal_refresh_builder.py | 31 + .../tests/test_us_nsece_childcare.py | 337 +- .../tests/test_us_spine_blindness.py | 9 +- tools/build_us_fiscal_refresh_release.py | 29 +- tools/prepare_us_childcare_attendance.py | 11 +- tools/validate_us_childcare_population.py | 66 +- tools/validate_us_childcare_sensitivity.py | 184 + 23 files changed, 6806 insertions(+), 128 deletions(-) create mode 100644 experiments/us-childcare-attendance/review-artifact-verification.json create mode 100644 experiments/us-childcare-attendance/review-source-stage-validation.json create mode 100644 experiments/us-childcare-attendance/review-transport-sensitivity.json create mode 100644 experiments/us-childcare-attendance/review-validation-criteria.txt create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py create mode 100644 tools/validate_us_childcare_sensitivity.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index 80562f711..b4cbc17da 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -12,7 +12,7 @@ The build runs `with_us_childcare_attendance_inputs` after the childcare expense producer and before release validation. The generated release input contract requires all three attendance columns. Licensed local source paths are explicit build inputs; CI does not download or redistribute survey records. This PR -provides build integration and a qualified local population candidate; it does +provides build integration and a local population candidate under review; it does not publish a replacement population or certify national CCDF spending. ## Source and mapping @@ -75,6 +75,11 @@ A fitted mixture of independent child ranks and a shared household rank models sibling dependence while preserving each child's conditional donor distribution. It is fitted on youngest sibling pairs in fully observed households using household weights and evaluated with household-separated folds. +The same rank also couples days and hours. The expanded validation therefore +integrates the actual weighted donor distributions to test joint day/hour +moments, correlations, and every child's contribution to totals in households +with three or more children. Its provisional screens currently fail; matching +the average participation rate does not qualify this household model. ## ASEC target harmonization @@ -82,6 +87,9 @@ household weights and evaluated with household-separated folds. `A_LINENO` within physical households. It counts measured last-week work among parents of any under-13 household child, matching the NSECE unit. Unrelated working adults do not become parents. Dangling parent pointers fail. +Missing household source identities, blank IDs, stringified nulls, and unresolved +person-to-household links fail before shared ranks are assigned. Unrelated +households must never acquire one shared `"nan"` identity. Regions derive from the shared Census state mapping. Household income is the sum of raw `PTOTVAL`, expressed in 2023 dollars using annual CPI-U. The pinned @@ -118,6 +126,28 @@ Native export adds only the three inputs and a receipt, reloads the result, and verifies every original entity column, household weight, and time period. Code hashes and environment versions accompany the aggregate preparation report. +The receipt binds the source hashes, contract, recipe code, runtime versions, +seed, matching/bridge settings, fitted dependence, and outside-domain policy to +each person's ID, household link, age, and three attendance values. Both native +US loaders restore and check it. A missing, stale, or altered receipt fails; +changing seed or settings requires rebuilding from the original parent. An +identical rerun verifies and reuses the existing values. A production-stage +input with existing under-13 attendance and no production receipt is rejected, +including all-zero columns; use the original unmodified parent. The lower-level +observed-cell imputer remains available for separately sourced observations. + +Calibration weights and row selection/order may change without invalidating +retained people. Changed IDs, membership, ages or attendance require a new source +execution. The final fiscal export checks every row for completeness, bounds, +integral monthly days, coherent zero schedules, and its source binding before +writing. Generic coverage overrides cannot waive this check. The written native +file receives the receipt and is reloaded and checked before source evidence is +reported. Private per-person hashes stay in local checkpoints/H5; public reports +contain only aggregate receipt summaries. The private inventory is a sequence +of ID/hash pairs: population-sized dictionaries cause quadratic traversal in +the Frame metadata container, which is intended for small mappings. These hashes detect accidental stale +or modified artifacts; they are not signatures or publication authorization. + Supply the same source inputs to the normal fiscal build using `--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, `--childcare-attendance-asec-cache`, and @@ -151,6 +181,30 @@ state subsidy variables avoid conflating attendance with the separate household aggregation issue [PolicyEngine-US #9405](https://github.com/PolicyEngine/policyengine-us/issues/9405). Outputs describe potential modeled benefits, not caseload or spending estimates. +Run the separate noncalendar assumption stress test with the same parent and +source files: + +```bash +uv run python tools/validate_us_childcare_sensitivity.py \ + --parent-h5 /local/populace_us_2024.h5 \ + --parent-sha256 48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --asec-source-cache /local/asec --seed 915 \ + --year 2026 --report /local/transport-sensitivity.json +``` + +The alternatives remove modeled irregular hours or shift modeled attended days +by one in either direction, subject to hours/day feasibility. Each preserves +measured regular hours. All arms use the same source population, matching fields, +weights and random seed; modified bridge donors are transferred again. These +are assumption stress tests, not confidence intervals. The +[declared diagnostic screens](../experiments/us-childcare-attendance/review-validation-criteria.txt) +flag national changes above 10% or state changes above 20% against the candidate, +and separately assess sibling schedule distributions. They were written before +the expanded runs, after seeing earlier development diagnostics, and require +review rather than serving as automatic release acceptance. + ## Validation and limits See [the aggregate experiment](../experiments/us-childcare-attendance/README.md). diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index 3cdfeae4f..b831ed251 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,4 +1,107 @@ -# NSECE attendance population qualification — 2026-09-12 +# NSECE attendance review revision — 2026-09-15 + +**PR #916 remains draft.** The engineering safeguards have been strengthened, +but the expanded household diagnostics fail provisional statistical screens. +The earlier aggregate means did not establish population validity. Nothing in +these reports authorizes publication or changes PolicyEngine-US defaults. + +- [Criteria declared before the expanded runs](review-validation-criteria.txt) +- [Revised source/stage and household diagnostics](review-source-stage-validation.json) +- [All-state noncalendar sensitivity results](review-transport-sensitivity.json) +- [Final artifact verification](review-artifact-verification.json) +- [Reproduction commands and receipt contract](../../docs/us-childcare-attendance.md) + +The criteria are developmental screens informed by earlier diagnostics, not an +untouched external evaluation or a maintainer-approved release standard. + +## Response to review + +- **C1:** bind attendance to source/recipe/runtime/settings and person-level + content; restore and verify the binding in both native loaders. Reject stale, + missing or changed receipts. An identical rerun validates existing values; + changed source/seed/policy requires rebuilding from the original parent. +- **A1:** recheck every row's completeness and valid schedule at final fiscal + export, independently of optional source flags or generic coverage overrides. + Persist and verify the receipt after native serialization. +- **A2:** reject missing/blank household identities and unresolved household + links before converting IDs to strings or assigning shared ranks. +- **A3:** evaluate the actual shared-rank schedule mixture, including days/hours + cross-moments and correlations and all-child totals for larger households. + The expanded evidence exposes a remaining model limitation; it does not close + this statistical concern. +- **S1:** declare screens and measure benefit sensitivity while preserving every + measured regular-hour value. The state-level sensitivity remains unresolved; + missing days and irregular care are not identified by this experiment. +- **S2:** record the actual calendar, sibling fit, bridge, predictor + harmonization, transfer and outside-domain operations in execution order. + +## Expanded household results + +Five household-separated folds contain 1,941 complete sibling households, +including 661 with three or more children. Predictions integrate the empirical +weighted donor CDFs exactly; no favorable simulation seed is selected. + +| Quantity | Observed | Shared-rank model | +| --- | ---: | ---: | +| Youngest-pair days correlation | 0.580 | 0.442 | +| Youngest-pair weekly-hours correlation | 0.523 | 0.356 | +| Mean total days/week, households with 3+ children | 4.510 | 5.653 | +| Mean total hours/week, households with 3+ children | 33.085 | 40.814 | + +Nine of fifteen provisional screens fail. Larger-household mean total days are +25.35% too high and hours 23.36% too high. A fitted binary-participation mixture +is not enough to establish realistic household schedules. A next model revision +should investigate household-size conditioning and joint schedule donors, then +be evaluated with separately reserved household evidence; retuning on these +folds would not create independent validation. + +## Noncalendar assumption sensitivity + +All 51 jurisdictions use the same parent, source, matching fields, survey +weights, seed and policy year. Each alternative modifies only modeled bridge +components and retransfers the resulting joint schedules; measured regular +hours are unchanged. Source-selection, missing partial calendars and true summer +attendance are separate uncertainties this stress test does not resolve. + +| Scenario | National annual potential benefits | Change from candidate | +| --- | ---: | ---: | +| Current candidate | $5.286 billion | — | +| No modeled irregular hours | $5.209 billion | −1.46% | +| One fewer modeled day | $5.222 billion | −1.22% | +| One more modeled day | $5.298 billion | +0.22% | + +State flags above 20% include TN (−36.0%, no irregular hours), IA (+60.0%), +KS (−49.4%) and MS (−69.6%) with one fewer day, and AR (+43.9%) with one +more day. OK also flags at −40.0%, but that is only a $2.06 change from an +approximately $5.15 baseline and must not be read as a material spending result. +Day changes also change daily hours and can cross state policy thresholds; +these are joint-schedule assumption tests, not monotonic attendance effects. +These estimates are potential modeled benefits, not calibrated CCDF expenditure, +caseload estimates or confidence intervals. + +## Revision code checks + +The 750-test regression run passed, covering the complete source-spine +architecture guard, pool-tool regressions, NSECE source/receipt behavior, and +the unconditional fiscal export guard. The preceding focused attendance and +release-coverage run passed 165 tests; native H5, serializer and fiscal-builder +checks also passed before the architecture fixes were verified in the final run. +After replacing the large receipt dictionary with a sequence, all 67 focused +source/receipt and architecture tests passed again. This avoids quadratic +traversal in immutable Frame metadata without changing attendance values. +Repository lint, format checks, the tracked CI inventory and the exact +42,159-field/41-inventory coverage audit passed. The build wheel was rebuilt; +its three new modules match the source bytes and import from the unpacked wheel. +These code checks do not certify restricted data or resolve the statistical gaps. + +## Historical evidence + +The September 12–13 reports below describe the previous execution and remain +available for audit. Their artifact receipts predate the content-binding +contract; rebuild them from the original parent before using the revised release +path. The attendance point estimates are unchanged by the engineering revision. + +## Original population candidate — 2026-09-12 Related: [#915](https://github.com/PolicyEngine/microcosm/issues/915) and [PR #916](https://github.com/PolicyEngine/microcosm/pull/916). diff --git a/experiments/us-childcare-attendance/review-artifact-verification.json b/experiments/us-childcare-attendance/review-artifact-verification.json new file mode 100644 index 000000000..54878c4be --- /dev/null +++ b/experiments/us-childcare-attendance/review-artifact-verification.json @@ -0,0 +1,47 @@ +{ + "date": "2026-09-15", + "people": 166321, + "households": 57240, + "under13_children": 31889, + "checkpoint_sha256": "e9d363aa5a10a611c751c704017edd47c6ec8c2ba583bb7124b27514ba15d69e", + "native_sha256": "937c0c0c9798f0cf9e015fc4d5bed204342480bf93a415d94d3080d66f20cb6f", + "previous_native_sha256": "be04c3fe14763bb5d40fbb7057763cec6ffb69e0a43a021c2d0b117d881b54d7", + "source_stage_report_sha256": "3d54dd35857a825679eec8a0d97e352541a673e257ce886aaf37d9c1a8b6bf9a", + "sensitivity_report_sha256": "05c0bedf2b59de80e0645005e4e10ec46c083dfe425f5d88342172af2812bff5", + "attendance_recipe": { + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "bf9ff3a1992d16c75fc74b8c17634791739669f0ecab4382fdf59a6e6c3c424c" + }, + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + } + }, + "content_binding_sha256": "db79754d13221f083d9ee56bd2efbc293039790468c8145ceea4755aa3b21d41", + "verified_native_loaders": [ + "microcosm.build.us_runtime.h5_io.load_legacy_calibrated_us_h5", + "microcosm.build.us_runtime.l0_refit_export.load_us_frame" + ], + "exact_attendance_matches_sensitivity_candidate": true, + "exact_sibling_diagnostics_match_previous_execution": true, + "household_weights_unchanged_on_native_reload": true, + "original_columns_and_period_verified_by_source_stage_export": true, + "difference_since_sensitivity_run": "Equivalent explicit column reindexing and type annotations for the repository AST guard, plus a sequence-based private receipt inventory to avoid quadratic metadata serialization; final attendance arrays and diagnostic values compare exactly.", + "build_wheel_sha256": "bf027a1814f8e374496d3ca42d4ed4b432e5a5e45290480f6c1c62861c3a0cde", + "verification_code_sha256": "203ef9ddacc8bb579b3acbe8a754fac5b2781f2f14f9b6125d179f5a94fb545d", + "changed_recipe_files_since_sensitivity": [ + "childcare_attendance_stage.py", + "childcare_attendance_receipt.py" + ], + "sensitivity_model_and_evaluator_code_unchanged": true, + "engine_version": "1.819.0", + "production_ready": false +} diff --git a/experiments/us-childcare-attendance/review-source-stage-validation.json b/experiments/us-childcare-attendance/review-source-stage-validation.json new file mode 100644 index 000000000..e23bab723 --- /dev/null +++ b/experiments/us-childcare-attendance/review-source-stage-validation.json @@ -0,0 +1,1921 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "source_child_count": 11745, + "source_attrition": [ + { + "status": "age_out_of_scope", + "n": 134, + "child_weight": 633390.6468148227 + }, + { + "status": "ambiguous_calendar", + "n": 882, + "child_weight": 3918044.035953992 + }, + { + "status": "complete", + "n": 7460, + "child_weight": 32249785.682044942 + }, + { + "status": "missing_calendar", + "n": 3095, + "child_weight": 14061828.648228496 + }, + { + "status": "partial_calendar", + "n": 174, + "child_weight": 847769.9869632281 + } + ], + "under13_complete_weight_share": 0.6313901604255374, + "seed": 915, + "evaluation_design": "fixed household diagnostic split; not final population certification", + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "training_children": 5988, + "heldout_children": 1472, + "unsupported_heldout_children": 30, + "unsupported_heldout_weight": 153817.788204796, + "household_overlap": 0, + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1442, + "observed": { + "participation": 0.47956011362829676, + "days_per_week": 1.94220990502671, + "hours_per_week": 14.670259928481958 + }, + "predicted": { + "participation": 0.4547372440086608, + "days_per_week": 1.7986704844660344, + "hours_per_week": 13.001295500494344 + } + }, + { + "grouping": "age", + "group": "0", + "n": 87, + "observed": { + "participation": 0.4749769477860246, + "days_per_week": 1.8705229048944185, + "hours_per_week": 15.234832555457738 + }, + "predicted": { + "participation": 0.21347668238234924, + "days_per_week": 0.7762397588546358, + "hours_per_week": 6.331944692101247 + } + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed": { + "participation": 0.6026124718926947, + "days_per_week": 2.2004884644301415, + "hours_per_week": 16.058889950232714 + }, + "predicted": { + "participation": 0.5817111215090339, + "days_per_week": 2.3867359768549528, + "hours_per_week": 20.29503642359935 + } + }, + { + "grouping": "age", + "group": "2", + "n": 107, + "observed": { + "participation": 0.6827728698846081, + "days_per_week": 3.2446932739752876, + "hours_per_week": 32.643365116161064 + }, + "predicted": { + "participation": 0.721108922787169, + "days_per_week": 2.9279814704204314, + "hours_per_week": 25.4484971084278 + } + }, + { + "grouping": "age", + "group": "3", + "n": 105, + "observed": { + "participation": 0.5478113606887365, + "days_per_week": 2.4737318877881727, + "hours_per_week": 18.774571500322175 + }, + "predicted": { + "participation": 0.7525857598038783, + "days_per_week": 3.556893850206531, + "hours_per_week": 31.34897414405891 + } + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed": { + "participation": 0.6853382748904278, + "days_per_week": 3.133535876664359, + "hours_per_week": 22.72675588135229 + }, + "predicted": { + "participation": 0.6264622224091229, + "days_per_week": 2.555534029105278, + "hours_per_week": 22.558086951188297 + } + }, + { + "grouping": "age", + "group": "5", + "n": 109, + "observed": { + "participation": 0.45965467004039157, + "days_per_week": 2.204288116474054, + "hours_per_week": 16.671100530443887 + }, + "predicted": { + "participation": 0.5197893305690322, + "days_per_week": 2.0628456061601703, + "hours_per_week": 14.660711473842793 + } + }, + { + "grouping": "age", + "group": "6", + "n": 121, + "observed": { + "participation": 0.37767600901948256, + "days_per_week": 1.4233675361325482, + "hours_per_week": 11.368361596236443 + }, + "predicted": { + "participation": 0.3812603191662228, + "days_per_week": 1.344172188177959, + "hours_per_week": 6.687190574415363 + } + }, + { + "grouping": "age", + "group": "7", + "n": 121, + "observed": { + "participation": 0.2903318905157446, + "days_per_week": 0.9137564001326794, + "hours_per_week": 4.425088819370026 + }, + "predicted": { + "participation": 0.3316072853951072, + "days_per_week": 1.3098426672238894, + "hours_per_week": 7.381236186215511 + } + }, + { + "grouping": "age", + "group": "8", + "n": 136, + "observed": { + "participation": 0.33161676756925296, + "days_per_week": 1.2251387657453359, + "hours_per_week": 6.71788070980216 + }, + "predicted": { + "participation": 0.35465278512495324, + "days_per_week": 1.3642638058954168, + "hours_per_week": 9.725344329820171 + } + }, + { + "grouping": "age", + "group": "9", + "n": 101, + "observed": { + "participation": 0.4569510479920196, + "days_per_week": 1.674346958462562, + "hours_per_week": 7.805739183708693 + }, + "predicted": { + "participation": 0.396876880067868, + "days_per_week": 1.3966673442424729, + "hours_per_week": 8.701466574792914 + } + }, + { + "grouping": "age", + "group": "10", + "n": 105, + "observed": { + "participation": 0.45503949077471245, + "days_per_week": 1.5782906105470733, + "hours_per_week": 12.426848636692846 + }, + "predicted": { + "participation": 0.17752112044794535, + "days_per_week": 0.6469841431239514, + "hours_per_week": 3.596759032644189 + } + }, + { + "grouping": "age", + "group": "11", + "n": 119, + "observed": { + "participation": 0.41662598750149954, + "days_per_week": 1.8643111020323588, + "hours_per_week": 12.941021091087745 + }, + "predicted": { + "participation": 0.4008574857176407, + "days_per_week": 1.339718142783517, + "hours_per_week": 7.024111200026524 + } + }, + { + "grouping": "age", + "group": "12", + "n": 103, + "observed": { + "participation": 0.46408861423609865, + "days_per_week": 1.4945247924023801, + "hours_per_week": 11.114322042194766 + }, + "predicted": { + "participation": 0.43591150819042584, + "days_per_week": 1.7008936429461807, + "hours_per_week": 7.10555160431239 + } + }, + { + "grouping": "region", + "group": "1", + "n": 187, + "observed": { + "participation": 0.5340539220646277, + "days_per_week": 2.4009930157522064, + "hours_per_week": 14.79000641069395 + }, + "predicted": { + "participation": 0.6359603067185889, + "days_per_week": 2.7160385976574153, + "hours_per_week": 21.24487930853725 + } + }, + { + "grouping": "region", + "group": "2", + "n": 335, + "observed": { + "participation": 0.46285701128880247, + "days_per_week": 1.8453473655274784, + "hours_per_week": 15.352184287431765 + }, + "predicted": { + "participation": 0.41283532735257467, + "days_per_week": 1.4868890394545802, + "hours_per_week": 9.329182591248077 + } + }, + { + "grouping": "region", + "group": "3", + "n": 470, + "observed": { + "participation": 0.4328632075166446, + "days_per_week": 1.8236781494293404, + "hours_per_week": 15.35364160426729 + }, + "predicted": { + "participation": 0.4184231306502971, + "days_per_week": 1.5947483523709618, + "hours_per_week": 10.342311458512322 + } + }, + { + "grouping": "region", + "group": "4", + "n": 450, + "observed": { + "participation": 0.5171827642556369, + "days_per_week": 1.8475690827448323, + "hours_per_week": 13.04393396272379 + }, + "predicted": { + "participation": 0.4063739455351018, + "days_per_week": 1.670368549893466, + "hours_per_week": 13.748230642009998 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 13, + "observed": { + "participation": 0.253295875991658, + "days_per_week": 0.8671327195533685, + "hours_per_week": 3.515415042668932 + }, + "predicted": { + "participation": 0.12949135028799522, + "days_per_week": 0.5134069370080796, + "hours_per_week": 3.2849650228565768 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 150, + "observed": { + "participation": 0.2939873489805854, + "days_per_week": 1.1857568669020946, + "hours_per_week": 10.001769026104405 + }, + "predicted": { + "participation": 0.20052896295212805, + "days_per_week": 0.7861821143968067, + "hours_per_week": 6.547539835013359 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 543, + "observed": { + "participation": 0.2512596429034898, + "days_per_week": 0.8664048151368835, + "hours_per_week": 5.612021341518408 + }, + "predicted": { + "participation": 0.30234830539829555, + "days_per_week": 0.9663278582430083, + "hours_per_week": 5.557806485223704 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 736, + "observed": { + "participation": 0.6462583482271946, + "days_per_week": 2.714084969528804, + "hours_per_week": 21.02161873348801 + }, + "predicted": { + "participation": 0.5856324643226721, + "days_per_week": 2.4572831629804637, + "hours_per_week": 18.54158986890966 + } + } + ], + "production_ready": false, + "limitations": [ + "Only complete, unambiguous calendars enter this holdout; source selection remains unvalidated.", + "The diagnostic matches age and caller-specified covariates; it is not a nationally validated fitted model.", + "Sibling assignments, mixed providers, older-child care and scalar monthly conversion need validation.", + "No full Microcosm population build or state CCDF distribution is certified by this source-only report." + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "selection_and_cross_validation": { + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "seed": 271828, + "design": "five-fold household-separated conditional-expectation diagnostic", + "coverage": [ + { + "grouping": "questionnaire_version", + "group": "1", + "n": 8516, + "weight": 37015599.704962164, + "usable_weight_share": 0.8712484989868113 + }, + { + "grouping": "questionnaire_version", + "group": "2", + "n": 1716, + "weight": 7945306.22844611, + "usable_weight_share": 0.0 + }, + { + "grouping": "questionnaire_version", + "group": "3", + "n": 1379, + "weight": 6116522.419782387, + "usable_weight_share": 0.0 + }, + { + "grouping": "age", + "group": "0", + "n": 709, + "weight": 3159547.2696341733, + "usable_weight_share": 0.6556513621184303 + }, + { + "grouping": "age", + "group": "1", + "n": 856, + "weight": 3710311.739747545, + "usable_weight_share": 0.6518631977426506 + }, + { + "grouping": "age", + "group": "2", + "n": 906, + "weight": 4112848.861546397, + "usable_weight_share": 0.6567885484115844 + }, + { + "grouping": "age", + "group": "3", + "n": 927, + "weight": 4191268.180093972, + "usable_weight_share": 0.5836151261043054 + }, + { + "grouping": "age", + "group": "4", + "n": 890, + "weight": 3761621.476875998, + "usable_weight_share": 0.6069150244376095 + }, + { + "grouping": "age", + "group": "5", + "n": 884, + "weight": 3759953.5032736305, + "usable_weight_share": 0.6441250249176611 + }, + { + "grouping": "age", + "group": "6", + "n": 954, + "weight": 4259102.717846392, + "usable_weight_share": 0.6213110691400029 + }, + { + "grouping": "age", + "group": "7", + "n": 944, + "weight": 4191490.05150999, + "usable_weight_share": 0.6014263712726516 + }, + { + "grouping": "age", + "group": "8", + "n": 925, + "weight": 3969583.419307067, + "usable_weight_share": 0.6776062606911202 + }, + { + "grouping": "age", + "group": "9", + "n": 921, + "weight": 4036325.188020362, + "usable_weight_share": 0.6661696977311178 + }, + { + "grouping": "age", + "group": "10", + "n": 884, + "weight": 3707790.894951107, + "usable_weight_share": 0.6220814618665044 + }, + { + "grouping": "age", + "group": "11", + "n": 908, + "weight": 4069233.422108399, + "usable_weight_share": 0.6084497314858798 + }, + { + "grouping": "age", + "group": "12", + "n": 903, + "weight": 4148351.6282756263, + "usable_weight_share": 0.6217216150291205 + }, + { + "grouping": "region", + "group": "1", + "n": 1468, + "weight": 8153076.12536276, + "usable_weight_share": 0.6401722331669089 + }, + { + "grouping": "region", + "group": "2", + "n": 2680, + "weight": 10714972.208999828, + "usable_weight_share": 0.6330942004076335 + }, + { + "grouping": "region", + "group": "3", + "n": 4275, + "weight": 20022339.81304313, + "usable_weight_share": 0.6249460927360144 + }, + { + "grouping": "region", + "group": "4", + "n": 3188, + "weight": 12187040.205784943, + "usable_weight_share": 0.634603874645437 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 188, + "weight": 819575.738509818, + "usable_weight_share": 0.646736557730207 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1470, + "weight": 5301911.400981113, + "usable_weight_share": 0.6698890755679009 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 3380, + "weight": 14009836.058523985, + "usable_weight_share": 0.7212981495115555 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 6573, + "weight": 30946105.155175745, + "usable_weight_share": 0.5836849144158337 + }, + { + "grouping": "income_band", + "group": "0", + "n": 120, + "weight": 497291.815717528, + "usable_weight_share": 0.602001589054288 + }, + { + "grouping": "income_band", + "group": "1", + "n": 2057, + "weight": 7466809.932490364, + "usable_weight_share": 0.6413274998297077 + }, + { + "grouping": "income_band", + "group": "2", + "n": 2904, + "weight": 10880663.16706181, + "usable_weight_share": 0.6487048847817203 + }, + { + "grouping": "income_band", + "group": "3", + "n": 2558, + "weight": 11325516.809686286, + "usable_weight_share": 0.6401496463974632 + }, + { + "grouping": "income_band", + "group": "4", + "n": 3134, + "weight": 15757011.988844048, + "usable_weight_share": 0.6309648019074636 + }, + { + "grouping": "income_band", + "group": "5", + "n": 838, + "weight": 5150134.639390623, + "usable_weight_share": 0.5652783862122169 + } + ], + "cross_validation": [ + { + "grouping": "all", + "group": "all", + "n": 7460, + "observed": { + "participation": 0.46514845107061753, + "days": 1.8750709563867467, + "hours": 14.240422572045741 + }, + "expected": { + "participation": 0.4654844290416027, + "days": 1.878527294011518, + "hours": 14.081114700859393 + } + }, + { + "grouping": "age", + "group": "0", + "n": 458, + "observed": { + "participation": 0.40007662898763713, + "days": 1.5207410031354076, + "hours": 12.391341719561105 + }, + "expected": { + "participation": 0.42043937266485903, + "days": 1.5581186938006875, + "hours": 12.778280226573155 + } + }, + { + "grouping": "age", + "group": "1", + "n": 585, + "observed": { + "participation": 0.5768442364096245, + "days": 2.3247221464109487, + "hours": 18.93194004904792 + }, + "expected": { + "participation": 0.5636534387398192, + "days": 2.2787632897750942, + "hours": 18.46136873433321 + } + }, + { + "grouping": "age", + "group": "2", + "n": 599, + "observed": { + "participation": 0.6119472630516792, + "days": 2.661365110302835, + "hours": 23.568671427585098 + }, + "expected": { + "participation": 0.615171451571855, + "days": 2.6668583554129937, + "hours": 24.008204825875836 + } + }, + { + "grouping": "age", + "group": "3", + "n": 597, + "observed": { + "participation": 0.6106570906701301, + "days": 2.6641302585227766, + "hours": 21.96346327818111 + }, + "expected": { + "participation": 0.6452189769582379, + "days": 2.8385499518769803, + "hours": 23.234965023661523 + } + }, + { + "grouping": "age", + "group": "4", + "n": 550, + "observed": { + "participation": 0.7046928755645677, + "days": 3.1467359328380096, + "hours": 25.30894736806148 + }, + "expected": { + "participation": 0.7168364294128767, + "days": 3.1704359925035326, + "hours": 25.23962440956216 + } + }, + { + "grouping": "age", + "group": "5", + "n": 581, + "observed": { + "participation": 0.5580840489623686, + "days": 2.527957605747102, + "hours": 20.351246817207983 + }, + "expected": { + "participation": 0.5580783794454302, + "days": 2.4817744855985993, + "hours": 19.696005730111843 + } + }, + { + "grouping": "age", + "group": "6", + "n": 611, + "observed": { + "participation": 0.38410943637037254, + "days": 1.5233703763582376, + "hours": 9.272995362839891 + }, + "expected": { + "participation": 0.3704517476571586, + "days": 1.4435320487985335, + "hours": 8.308626409800924 + } + }, + { + "grouping": "age", + "group": "7", + "n": 586, + "observed": { + "participation": 0.34047335603475937, + "days": 1.267585660002729, + "hours": 8.978033563109225 + }, + "expected": { + "participation": 0.32048663598972876, + "days": 1.2092330340598734, + "hours": 7.976819608666644 + } + }, + { + "grouping": "age", + "group": "8", + "n": 611, + "observed": { + "participation": 0.37000227568041516, + "days": 1.4234519706143929, + "hours": 8.822905923973602 + }, + "expected": { + "participation": 0.37441818500322505, + "days": 1.4678570808250095, + "hours": 8.581931573672641 + } + }, + { + "grouping": "age", + "group": "9", + "n": 590, + "observed": { + "participation": 0.3862968300759907, + "days": 1.373131779820256, + "hours": 9.825473717875598 + }, + "expected": { + "participation": 0.3658218612710925, + "days": 1.2745107095149821, + "hours": 9.212380152023707 + } + }, + { + "grouping": "age", + "group": "10", + "n": 541, + "observed": { + "participation": 0.39185689727904155, + "days": 1.3647219850376735, + "hours": 8.468134791861964 + }, + "expected": { + "participation": 0.3879904743599543, + "days": 1.313066433397897, + "hours": 7.6091158562940935 + } + }, + { + "grouping": "age", + "group": "11", + "n": 565, + "observed": { + "participation": 0.3768238596848206, + "days": 1.3633597325953704, + "hours": 8.143708694787577 + }, + "expected": { + "participation": 0.37027421084947587, + "days": 1.3896706621501198, + "hours": 8.180743029734462 + } + }, + { + "grouping": "age", + "group": "12", + "n": 586, + "observed": { + "participation": 0.35838594666001994, + "days": 1.3239431239565607, + "hours": 10.203528792841063 + }, + "expected": { + "participation": 0.37173621674013835, + "days": 1.447846572682685, + "hours": 10.94364602719194 + } + }, + { + "grouping": "region", + "group": "1", + "n": 929, + "observed": { + "participation": 0.4988625902951673, + "days": 2.101321934458425, + "hours": 14.697745432365767 + }, + "expected": { + "participation": 0.5341920187102704, + "days": 2.234373766910041, + "hours": 15.515545077772881 + } + }, + { + "grouping": "region", + "group": "2", + "n": 1654, + "observed": { + "participation": 0.495642471526947, + "days": 1.9378635600824832, + "hours": 14.438707404977198 + }, + "expected": { + "participation": 0.48657470779470846, + "days": 1.9053160452954483, + "hours": 13.830397580793818 + } + }, + { + "grouping": "region", + "group": "3", + "n": 2752, + "observed": { + "participation": 0.4310162776090592, + "days": 1.7482660126296112, + "hours": 14.16760866653331 + }, + "expected": { + "participation": 0.4254047590539008, + "days": 1.728432288982956, + "hours": 13.771990873343658 + } + }, + { + "grouping": "region", + "group": "4", + "n": 2125, + "observed": { + "participation": 0.470872102608504, + "days": 1.872465315119329, + "hours": 13.875678601794997 + }, + "expected": { + "participation": 0.46546295898430057, + "days": 1.857723153971244, + "hours": 13.833112421687238 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 122, + "observed": { + "participation": 0.34614706740243706, + "days": 1.1849281571656916, + "hours": 9.788566562075097 + }, + "expected": { + "participation": 0.373389275855184, + "days": 1.462386368172592, + "hours": 12.574282386813668 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1016, + "observed": { + "participation": 0.3096578455166947, + "days": 1.244408501915661, + "hours": 10.193456359940383 + }, + "expected": { + "participation": 0.2882996076692788, + "days": 1.1762845771341546, + "hours": 10.282450861819337 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 2418, + "observed": { + "participation": 0.2864480974767621, + "days": 0.9529380212414282, + "hours": 6.42952602558992 + }, + "expected": { + "participation": 0.29059369126489437, + "days": 0.9858655333196232, + "hours": 6.764091600272923 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 3904, + "observed": { + "participation": 0.5991891288528669, + "days": 2.535220374688929, + "hours": 19.536646150926547 + }, + "expected": { + "participation": 0.6008699918772364, + "days": 2.528223299633135, + "hours": 18.96579429859326 + } + }, + { + "grouping": "income_band", + "group": "0", + "n": 76, + "observed": { + "participation": 0.19070472825408744, + "days": 0.8431547354115522, + "hours": 5.587881885800679 + }, + "expected": { + "participation": 0.25300464764173586, + "days": 0.9988467887623982, + "hours": 7.43593331362669 + } + }, + { + "grouping": "income_band", + "group": "1", + "n": 1378, + "observed": { + "participation": 0.35684525807866907, + "days": 1.5280931021110342, + "hours": 12.098088591903252 + }, + "expected": { + "participation": 0.3773650597658377, + "days": 1.627338456412184, + "hours": 12.706694579809993 + } + }, + { + "grouping": "income_band", + "group": "2", + "n": 1922, + "observed": { + "participation": 0.4194011939997811, + "days": 1.7068232976926216, + "hours": 13.491053608546364 + }, + "expected": { + "participation": 0.4119472083085292, + "days": 1.7030609232530567, + "hours": 13.262000601507708 + } + }, + { + "grouping": "income_band", + "group": "3", + "n": 1632, + "observed": { + "participation": 0.4246978564102466, + "days": 1.7238735833385048, + "hours": 14.076591113511121 + }, + "expected": { + "participation": 0.4255788471352766, + "days": 1.704797441331804, + "hours": 13.264862389978802 + } + }, + { + "grouping": "income_band", + "group": "4", + "n": 1977, + "observed": { + "participation": 0.5221816116808028, + "days": 1.9968956951019248, + "hours": 14.35832226780555 + }, + "expected": { + "participation": 0.519705526319959, + "days": 1.9988673657729517, + "hours": 14.55106672367201 + } + }, + { + "grouping": "income_band", + "group": "5", + "n": 475, + "observed": { + "participation": 0.6883939484468758, + "days": 2.9203317046618578, + "hours": 20.476264713601847 + }, + "expected": { + "participation": 0.6762909354194406, + "days": 2.8292574855634296, + "hours": 19.43897806431697 + } + } + ], + "matching_levels": { + "age,region,parent_work_status,income_band": 7278, + "age,parent_work_status,income_band": 150, + "age,parent_work_status": 32 + }, + "sibling_validation": [ + { + "fold": 0, + "training_rho": 0.7504775690168417, + "households": 351, + "weight": 1627200.3809817089, + "observed_both": 0.3346110552302055, + "independent_both": 0.26023741618799584, + "coupled_both": 0.3228394147816889 + }, + { + "fold": 1, + "training_rho": 0.8095401305423818, + "households": 367, + "weight": 1640369.17044373, + "observed_both": 0.308478237709926, + "independent_both": 0.2538314963354507, + "coupled_both": 0.31777192913378116 + }, + { + "fold": 2, + "training_rho": 0.7176223341350483, + "households": 443, + "weight": 1849383.6729044288, + "observed_both": 0.3831182075641927, + "independent_both": 0.260592140353083, + "coupled_both": 0.3144997720634755 + }, + { + "fold": 3, + "training_rho": 0.8946850037821993, + "households": 398, + "weight": 1567825.4608580691, + "observed_both": 0.2954259187077949, + "independent_both": 0.2551329149477867, + "coupled_both": 0.3186981148017382 + }, + { + "fold": 4, + "training_rho": 0.8022505236076816, + "households": 382, + "weight": 1644777.832273697, + "observed_both": 0.3129856591731878, + "independent_both": 0.2695651435301845, + "coupled_both": 0.3411406780146054 + } + ], + "questionnaire_transport": [ + { + "questionnaire_version": 2, + "scored_children": 1690, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.4166998042800092, + "regular_hours_per_week": 12.379208437570716 + }, + "expected": { + "regular_participation": 0.429264128691119, + "regular_hours_per_week": 14.800674181069196 + } + }, + { + "questionnaire_version": 3, + "scored_children": 1356, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.38447915314763226, + "regular_hours_per_week": 10.163827277430636 + }, + "expected": { + "regular_participation": 0.40786334693307874, + "regular_hours_per_week": 13.921490221932158 + } + } + ], + "assumptions": [ + "Calendar selection is ignorable conditional on matching fields; not identified from these data.", + "Main reference-week schedules transfer to May/fall typical weeks; summer attendance is not observed.", + "Regular-care comparisons cannot validate days or irregular care in instruments without calendars." + ], + "production_ready": false + }, + "masked_calendar_validation": { + "seed": 161803, + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1581, + "observed_days": 1.7176113066572272, + "imputed_days": 1.7931671544992573, + "observed_hours": 13.097316141278531, + "imputed_hours": 13.161738723028055 + }, + { + "grouping": "age", + "group": "0", + "n": 104, + "observed_days": 1.841126169101498, + "imputed_days": 1.757972840808916, + "observed_hours": 17.766331967357488, + "imputed_hours": 18.04127357697798 + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed_days": 2.37969546844872, + "imputed_days": 2.466724275790995, + "observed_hours": 19.829328945193588, + "imputed_hours": 19.122886717221327 + }, + { + "grouping": "age", + "group": "2", + "n": 141, + "observed_days": 2.4591773765558673, + "imputed_days": 2.5150278262191854, + "observed_hours": 22.536051090399965, + "imputed_hours": 22.20070627284854 + }, + { + "grouping": "age", + "group": "3", + "n": 123, + "observed_days": 2.3763627081083216, + "imputed_days": 2.276649667113583, + "observed_hours": 20.251977914853146, + "imputed_hours": 19.002839959330025 + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed_days": 3.137397964036117, + "imputed_days": 3.2507274717120613, + "observed_hours": 23.68360614421261, + "imputed_hours": 24.493916641155817 + }, + { + "grouping": "age", + "group": "5", + "n": 113, + "observed_days": 2.2242008794879244, + "imputed_days": 2.6293843819994196, + "observed_hours": 15.760856040214335, + "imputed_hours": 17.46780844312604 + }, + { + "grouping": "age", + "group": "6", + "n": 130, + "observed_days": 1.1830651060264654, + "imputed_days": 1.4124105587435267, + "observed_hours": 7.042175416036001, + "imputed_hours": 6.515374725210956 + }, + { + "grouping": "age", + "group": "7", + "n": 131, + "observed_days": 1.4073709771017413, + "imputed_days": 1.2967131682238184, + "observed_hours": 10.083939201157254, + "imputed_hours": 9.618996783594744 + }, + { + "grouping": "age", + "group": "8", + "n": 126, + "observed_days": 1.057579033652681, + "imputed_days": 1.0820232296805903, + "observed_hours": 8.256788366496435, + "imputed_hours": 6.9299612873656375 + }, + { + "grouping": "age", + "group": "9", + "n": 118, + "observed_days": 1.4349301372689758, + "imputed_days": 1.2570443106530333, + "observed_hours": 10.733568940718998, + "imputed_hours": 11.528485757775337 + }, + { + "grouping": "age", + "group": "10", + "n": 124, + "observed_days": 1.2058459898990705, + "imputed_days": 1.2067301677274491, + "observed_hours": 4.1774445652404655, + "imputed_hours": 4.342805855105561 + }, + { + "grouping": "age", + "group": "11", + "n": 120, + "observed_days": 0.9483350107584563, + "imputed_days": 1.2273618861530076, + "observed_hours": 5.566349482238208, + "imputed_hours": 6.3416466466885835 + }, + { + "grouping": "age", + "group": "12", + "n": 123, + "observed_days": 1.1181905452629102, + "imputed_days": 1.3377626847259545, + "observed_hours": 8.079018679724042, + "imputed_hours": 9.278796503817835 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 33, + "observed_days": 1.5430497710033617, + "imputed_days": 1.3274872915746017, + "observed_hours": 12.754941207431992, + "imputed_hours": 12.147679308677683 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 233, + "observed_days": 1.3163214930511598, + "imputed_days": 1.4661772450887058, + "observed_hours": 9.57367968242547, + "imputed_hours": 9.490248507911081 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 529, + "observed_days": 0.8935323002915262, + "imputed_days": 1.0535312696492358, + "observed_hours": 6.370457810059637, + "imputed_hours": 7.0815725747740155 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 786, + "observed_days": 2.3152602893352414, + "imputed_days": 2.332856367184494, + "observed_hours": 17.994069548324816, + "imputed_hours": 17.722049473088507 + } + ], + "regular_hours_preserved": true, + "limitations": "Tests reconstruction where calendars exist; not a direct test of unobserved days in the other questionnaire instruments." + }, + "sibling_schedule_validation": { + "design": "five household-separated folds; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "training_rho": [ + 0.7504775690168417, + 0.8095401305423818, + 0.7176223341350483, + 0.8946850037821993, + 0.8022505236076816 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.2599357218299179, + "correlation": 0.14356484664250233 + }, + "days": { + "joint_product": 4.562554958882675, + "correlation": 0.1514064619097721 + }, + "weekly_hours": { + "joint_product": 274.5482618143516, + "correlation": 0.1290575794049498 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3228241594928079, + "correlation": 0.39662161163709453 + }, + "days": { + "joint_product": 6.168391228908872, + "correlation": 0.44224735414975214 + }, + "weekly_hours": { + "joint_product": 393.50536137310974, + "correlation": 0.35635277128820114 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.26594831103314626, + "correlation": 0.1768906778018396 + }, + "days": { + "joint_product": 4.755017625871223, + "correlation": 0.1796772530434437 + }, + "weekly_hours": { + "joint_product": 288.78160178706247, + "correlation": 0.15022942733210107 + } + }, + "coupled": { + "participation": { + "joint_product": 0.32461663032314914, + "correlation": 0.41320572283269663 + }, + "days": { + "joint_product": 6.243213912824172, + "correlation": 0.44765187465853035 + }, + "weekly_hours": { + "joint_product": 406.53469377347056, + "correlation": 0.3715598543103152 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 4.742171858563125, + "sd_total_weekly_hours": 43.69183090611876, + "all_children_attend": 0.10870430564775237 + }, + "coupled": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 5.826012333228911, + "sd_total_weekly_hours": 52.0732028760817, + "all_children_attend": 0.19850142013240785 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.005764539166296878, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.13764128251529073, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.16710441607001986, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.04245779938404666, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.07023724634135614, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.051720923806702845, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.11411618300267751, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.19079301720982428, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.22780056599436455, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.20509336751041, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.2534916563112233, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.23360354492760613, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.010530264051684455, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.07370280213290151, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.0128154922623471, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "candidate_frame_written": true, + "environment": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "microcosm-build": "0.1.0", + "microcosm-frame": "0.1.0", + "policyengine-us": "1.819.0", + "python": "3.13.5" + }, + "code_sha256": { + "prepare_us_childcare_attendance.py": "ca4f504a652a9005f692cbd008f430fff0a25bbe32629b75eca80f0be0c7fa51", + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_sibling_validation.py": "791cf8171b9a673889d42ea9d43cf7396364a097308ae156acbb66b19a4523b1", + "nsece_childcare_dependence.py": "bf9ff3a1992d16c75fc74b8c17634791739669f0ecab4382fdf59a6e6c3c424c", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645", + "childcare_sensitivity.py": "a3d370bcaf78b609d81c542dc774b7dab0202ea02f53f80135417320840e06ac", + "nsece_childcare_assessment.py": "537d5d839ce9df4f1772ee887597811990aab5b7f10a863b70fe6630583fa3b7", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "childcare.py": "a8502e1fc15457a7f120936a8940fbb51cba3e2299d92ca9cd69b749f6deea2f", + "childcare_attendance_source.json": "c5beeeb09ab330c305e3250bc7db5a5003336d26fa508872211cb18958ab9e97", + "education_assistance_source.py": "e259e3fce64fdcab62953f79a1aa41719f81c64c7c5dc1a63f8b1e493ff5f83a", + "geography_constants.py": "dc31ec6aa913d638d51ccc8e3fda11edee6c3f508ea5d2e27690bdb43e081fd2", + "uv.lock": "751d5ef5d25406bbae1798667f0e29890d4aad933d323c12912c7d45d8809bb9" + }, + "candidate_checkpoint_sha256": "e9d363aa5a10a611c751c704017edd47c6ec8c2ba583bb7124b27514ba15d69e", + "parent_population_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "production_stage_executed": true, + "candidate_receipts": { + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "childcare_outside_domain_baseline": { + "engine_version": "1.819.0", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operations": [ + { + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance", + "age_min": 0, + "age_max": 12 + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "operation": "regular_hours_schedule_bridge", + "nearest_donors": 10 + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + }, + "childcare_attendance_binding": { + "schema_version": 1, + "binding_sha256": "db79754d13221f083d9ee56bd2efbc293039790468c8145ceea4755aa3b21d41", + "execution_sha256": "24a6971551002db8dc279f0f352cd16675101f5e668f3b04af16ce8c3411dfa8", + "source_people": 166321, + "retained_people": 166321, + "execution": { + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "bf9ff3a1992d16c75fc74b8c17634791739669f0ecab4382fdf59a6e6c3c424c", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "context": { + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "childcare_outside_domain_baseline": { + "engine_version": "1.819.0", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operations": [ + { + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance", + "age_min": 0, + "age_max": 12 + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "operation": "regular_hours_schedule_bridge", + "nearest_donors": 10 + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + } + } + } + } + }, + "native_candidate_written": true, + "native_candidate_sha256": "937c0c0c9798f0cf9e015fc4d5bed204342480bf93a415d94d3080d66f20cb6f" +} diff --git a/experiments/us-childcare-attendance/review-transport-sensitivity.json b/experiments/us-childcare-attendance/review-transport-sensitivity.json new file mode 100644 index 000000000..5e344b69b --- /dev/null +++ b/experiments/us-childcare-attendance/review-transport-sensitivity.json @@ -0,0 +1,3214 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "bf9ff3a1992d16c75fc74b8c17634791739669f0ecab4382fdf59a6e6c3c424c", + "childcare_attendance_stage.py": "8705b891c1bcbab8eb852d1b15c3dd51c496ceabd08924dfbe193d85c79edc2c", + "childcare_attendance_receipt.py": "2e914760d6012878d15bf32c4ca24426bb6fbbf86345b939a7945b4cdf0a251c" + } + }, + "seed": 915, + "engine_version": "1.819.0", + "policy_year": 2026, + "code_sha256": { + "validate_us_childcare_sensitivity.py": "59d41bf8b6192f9d4d01976e9a3b464446ade56d67f7493cb611ef3d8c01d07c", + "childcare_sensitivity.py": "a3d370bcaf78b609d81c542dc774b7dab0202ea02f53f80135417320840e06ac" + }, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "assumptions": { + "candidate": {}, + "no_modeled_irregular_hours": { + "include_modeled_irregular_hours": false, + "modeled_day_shift": 0, + "measured_regular_hours_preserved": true + }, + "one_fewer_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": -1, + "measured_regular_hours_preserved": true + }, + "one_more_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": 1, + "measured_regular_hours_preserved": true + } + }, + "changed_children": { + "candidate": 0, + "no_modeled_irregular_hours": 1809, + "one_fewer_day": 5705, + "one_more_day": 6683 + }, + "screens": { + "national_relative_change": 0.1, + "state_relative_change": 0.2 + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "no_modeled_irregular_hours": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_fewer_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_more_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "no_modeled_irregular_hours": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_fewer_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_more_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159527.697095435 + }, + "no_modeled_irregular_hours": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18733.29978438327, + "annual_modeled_benefits": 23159479.981134396 + }, + "one_fewer_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159508.09361713 + }, + "one_more_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 22260467.45209273 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "no_modeled_irregular_hours": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 17289.87330874073, + "annual_modeled_benefits": 50793412.084522665 + }, + "one_fewer_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 47298738.19780214 + }, + "one_more_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 73116589.09583077 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "no_modeled_irregular_hours": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 18502.54616478004, + "annual_modeled_benefits": 236445566.4573961 + }, + "one_fewer_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "one_more_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "no_modeled_irregular_hours": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "one_fewer_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "one_more_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "no_modeled_irregular_hours": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_fewer_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_more_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "no_modeled_irregular_hours": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "one_fewer_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "one_more_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 336693781.677099 + }, + "no_modeled_irregular_hours": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 76833.30946851184, + "annual_modeled_benefits": 320195353.52476215 + }, + "one_fewer_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 60231.18556508084, + "annual_modeled_benefits": 320401507.8530382 + }, + "one_more_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 60231.18556508084, + "annual_modeled_benefits": 324178392.2600601 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "no_modeled_irregular_hours": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_fewer_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_more_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 2554.6082648136944, + "annual_modeled_benefits": 15688867.198366832 + }, + "no_modeled_irregular_hours": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 2554.6082648136944, + "annual_modeled_benefits": 15688867.198366832 + }, + "one_fewer_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 2554.6082648136944, + "annual_modeled_benefits": 15688867.198366832 + }, + "one_more_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 2554.6082648136944, + "annual_modeled_benefits": 15688867.198366832 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "no_modeled_irregular_hours": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_fewer_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_more_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651167.105942965 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + }, + "no_modeled_irregular_hours": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + }, + "one_fewer_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786122.30308777 + }, + "one_more_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786118.53858995 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "no_modeled_irregular_hours": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_fewer_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_more_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13277.069981029548, + "annual_modeled_benefits": 54542065.75748161 + }, + "no_modeled_irregular_hours": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13277.069981029548, + "annual_modeled_benefits": 54542065.75748161 + }, + "one_fewer_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13277.069981029548, + "annual_modeled_benefits": 87267295.53406624 + }, + "one_more_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13277.069981029548, + "annual_modeled_benefits": 54542065.75748161 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "no_modeled_irregular_hours": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "one_fewer_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 29101934.00209515 + }, + "one_more_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709158.015559085 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + }, + "no_modeled_irregular_hours": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + }, + "one_fewer_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 157.98810727270265 + }, + "one_more_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 146.34993561158112 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + }, + "no_modeled_irregular_hours": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 69024044.59310351 + }, + "one_fewer_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 37176.93812166341, + "annual_modeled_benefits": 72842353.11263487 + }, + "one_more_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "no_modeled_irregular_hours": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_fewer_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_more_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 212.01572016369738 + }, + "no_modeled_irregular_hours": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 212.01572016369738 + }, + "one_fewer_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 212.01572016369738 + }, + "one_more_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 212.01572016369738 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "no_modeled_irregular_hours": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_fewer_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_more_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + }, + "no_modeled_irregular_hours": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + }, + "one_fewer_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + }, + "one_more_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + }, + "no_modeled_irregular_hours": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 7256.951193145127, + "annual_modeled_benefits": 30449886.57411563 + }, + "one_fewer_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 9247129.336387664 + }, + "one_more_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + }, + "no_modeled_irregular_hours": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + }, + "one_fewer_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110017.00214956 + }, + "one_more_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.31673183 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "no_modeled_irregular_hours": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_fewer_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_more_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "no_modeled_irregular_hours": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 750.2732835703686 + }, + "one_fewer_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "one_more_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "no_modeled_irregular_hours": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_fewer_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_more_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "no_modeled_irregular_hours": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_fewer_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_more_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + }, + "no_modeled_irregular_hours": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59644459.68607285 + }, + "one_fewer_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646236.386424586 + }, + "one_more_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647120.40554538 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "no_modeled_irregular_hours": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "one_fewer_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386147637.05533403 + }, + "one_more_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "no_modeled_irregular_hours": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_fewer_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_more_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + }, + "no_modeled_irregular_hours": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 465.6641840398204 + }, + "one_fewer_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 474.0045400403885 + }, + "one_more_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 515.8170557760147 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "no_modeled_irregular_hours": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "one_fewer_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "one_more_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109088290.29528335 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "no_modeled_irregular_hours": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "one_fewer_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 3.0897752604315514 + }, + "one_more_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "no_modeled_irregular_hours": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_fewer_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_more_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "no_modeled_irregular_hours": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "one_fewer_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "one_more_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "no_modeled_irregular_hours": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_fewer_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_more_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "no_modeled_irregular_hours": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 27854.604704734404, + "annual_modeled_benefits": 77020404.93038082 + }, + "one_fewer_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088803.73884593 + }, + "one_more_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + }, + "no_modeled_irregular_hours": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + }, + "one_fewer_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.483422956 + }, + "one_more_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809179.365034073 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + }, + "no_modeled_irregular_hours": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 37178.86130036996, + "annual_modeled_benefits": 76918628.29417688 + }, + "one_fewer_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178652.73669359 + }, + "one_more_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 58319.75094880746, + "annual_modeled_benefits": 139207804.7182324 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "no_modeled_irregular_hours": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_fewer_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_more_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + }, + "no_modeled_irregular_hours": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + }, + "one_fewer_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + }, + "one_more_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66285594.41326967 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + }, + "no_modeled_irregular_hours": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 707106120.8814734 + }, + "one_fewer_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 723295768.2248663 + }, + "one_more_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 32213.82430987344, + "annual_modeled_benefits": 735348229.2533482 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 249203866.21813098 + }, + "no_modeled_irregular_hours": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 45187.34378196369, + "annual_modeled_benefits": 249203861.9343761 + }, + "one_fewer_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 218587967.72536415 + }, + "one_more_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 45187.52193577867, + "annual_modeled_benefits": 254330473.5103389 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "no_modeled_irregular_hours": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_fewer_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_more_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.243193686 + }, + "no_modeled_irregular_hours": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329923.422902562 + }, + "one_fewer_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329930.062846346 + }, + "one_more_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 10521487.168013036 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "no_modeled_irregular_hours": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "one_fewer_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284617.12617347 + }, + "one_more_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 33918.61217102362, + "annual_modeled_benefits": 210174118.98339957 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "no_modeled_irregular_hours": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 2962.834582739495, + "annual_modeled_benefits": 18103300.895859934 + }, + "one_fewer_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "one_more_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18834318.50135846 + } + } + ], + "production_ready": false, + "interpretation": "assumption stress tests, not confidence intervals; potential modeled benefits, not calibrated spending", + "summary": { + "states_evaluated": 51, + "annual_potential_modeled_benefits": { + "baseline": 2253392072.9613476, + "candidate": 5286396512.037729, + "no_modeled_irregular_hours": 5209249263.466962, + "one_fewer_day": 5221847327.786389, + "one_more_day": 5298269288.494554 + }, + "sensitivity_flags": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.3599643633557116, + "absolute_change": -43260036.59620613, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "IA", + "relative_change": 0.5999998225607299, + "absolute_change": 32725229.776584633, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "KS", + "relative_change": -0.49350560992419995, + "absolute_change": -28355630.330927107, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "MS", + "relative_change": -0.6963165011569457, + "absolute_change": -21202761.328125004, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_more_day", + "state": "AR", + "relative_change": 0.43948945692098534, + "absolute_change": 22323171.50997054, + "flagged": true + } + ], + "comparisons": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "US", + "relative_change": -0.014593541819100081, + "absolute_change": -77147248.5707674, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AZ", + "relative_change": -2.0603166723927034e-06, + "absolute_change": -47.715961039066315, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AR", + "relative_change": -1.0830808059296869e-07, + "absolute_change": -5.501337565481663, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CA", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CO", + "relative_change": 1.5282148622892612e-07, + "absolute_change": 36.13395735621452, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "FL", + "relative_change": -0.04900128559000059, + "absolute_change": -16498428.152336836, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "LA", + "relative_change": -0.0524188932261243, + "absolute_change": -3818315.91796875, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MN", + "relative_change": -2.262688775133818e-09, + "absolute_change": -0.6219009757041931, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MS", + "relative_change": -1.343320763490555e-07, + "absolute_change": -4.090397037565708, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NE", + "relative_change": -0.002700483876552961, + "absolute_change": -2.0315871736966074, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NM", + "relative_change": -2.9711291489026368e-05, + "absolute_change": -1772.1665807962418, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ND", + "relative_change": -0.011427064224842841, + "absolute_change": -5.382682800292969, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SC", + "relative_change": -0.0008873514588288169, + "absolute_change": -68404.86783382297, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SD", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.3599643633557116, + "absolute_change": -43260036.59620613, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VT", + "relative_change": -0.01873457248239016, + "absolute_change": -13500252.330206513, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VA", + "relative_change": -1.7189760938327763e-08, + "absolute_change": -4.283754885196686, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WV", + "relative_change": -5.166482526594711e-07, + "absolute_change": -4.820291124284267, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WY", + "relative_change": -1.2466196861638054e-08, + "absolute_change": -0.22567931562662125, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "US", + "relative_change": -0.012210431832790832, + "absolute_change": -64549184.25133991, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AZ", + "relative_change": -8.464541488685182e-07, + "absolute_change": -19.60347830504179, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AR", + "relative_change": -0.06880181634068526, + "absolute_change": -3494679.3880580887, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CA", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "FL", + "relative_change": -0.04838899531469712, + "absolute_change": -16292273.824060798, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IL", + "relative_change": -6.29279612809565e-07, + "absolute_change": -123.8335727751255, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IA", + "relative_change": 0.5999998225607299, + "absolute_change": 32725229.776584633, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "KS", + "relative_change": -0.49350560992419995, + "absolute_change": -28355630.330927107, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "KY", + "relative_change": 0.06803920743236715, + "absolute_change": 10.06459831040911, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "LA", + "relative_change": -1.015677875316422e-07, + "absolute_change": -7.398437395691872, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MS", + "relative_change": -0.6963165011569457, + "absolute_change": -21202761.328125004, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "MO", + "relative_change": 5.114562408698159e-08, + "absolute_change": 2.256034232676029, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NM", + "relative_change": 7.601102031722338e-08, + "absolute_change": 4.533770941197872, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NY", + "relative_change": -6.160768878729368e-06, + "absolute_change": -2378.981001317501, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ND", + "relative_change": 0.006278936149423715, + "absolute_change": 2.957673200275167, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SC", + "relative_change": -7.860244217741888e-08, + "absolute_change": -6.059368714690208, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SD", + "relative_change": -7.935147357416053e-08, + "absolute_change": -0.46096683014184237, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TN", + "relative_change": -1.0113017502190059e-07, + "absolute_change": -12.153689414262772, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VT", + "relative_change": 0.0037321277096120254, + "absolute_change": 2689395.0131863356, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VA", + "relative_change": -0.122854829491162, + "absolute_change": -30615898.492766827, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WV", + "relative_change": 1.9503393942456014e-07, + "absolute_change": 1.81965265981853, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WI", + "relative_change": -1.474560751887755e-07, + "absolute_change": -36.7585366666317, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "US", + "relative_change": 0.002245911071897205, + "absolute_change": 11872776.456824303, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AZ", + "relative_change": -0.038820318650775495, + "absolute_change": -899060.2450027056, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AR", + "relative_change": 0.43948945692098534, + "absolute_change": 22323171.50997054, + "flagged": true + }, + { + "scenario": "one_more_day", + "state": "CA", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "FL", + "relative_change": -0.037171430237584874, + "absolute_change": -12515389.417038918, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ID", + "relative_change": -8.714356196712921e-06, + "absolute_change": -92.81887292861938, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IL", + "relative_change": -6.484094955654378e-07, + "absolute_change": -127.59807059168816, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KS", + "relative_change": 0.02178292270244223, + "absolute_change": 1251593.6825368255, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KY", + "relative_change": -0.010637750292372622, + "absolute_change": -1.5735733507124223, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "LA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MO", + "relative_change": -9.734376568026846e-09, + "absolute_change": -0.4293834939599037, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NM", + "relative_change": 1.4897049891328236e-05, + "absolute_change": 888.5528917387128, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ND", + "relative_change": 0.09504402234161904, + "absolute_change": 44.77018893590139, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OH", + "relative_change": 1.9287845264817622e-05, + "absolute_change": 2104.037481069565, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SD", + "relative_change": 2.445516903920343e-07, + "absolute_change": 1.4206442860886455, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TN", + "relative_change": 0.15834041628941534, + "absolute_change": 19029139.827849388, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VT", + "relative_change": 0.020457570998109725, + "absolute_change": 14741856.041668296, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VA", + "relative_change": 0.020571941238345593, + "absolute_change": 5126607.2922079265, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WV", + "relative_change": 0.1277136215585162, + "absolute_change": 1191558.9248193502, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WI", + "relative_change": -0.1568910652614762, + "absolute_change": -39110534.90131056, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WY", + "relative_change": 0.04038033587970585, + "absolute_change": 731017.3798192106, + "flagged": false + } + ] + } +} diff --git a/experiments/us-childcare-attendance/review-validation-criteria.txt b/experiments/us-childcare-attendance/review-validation-criteria.txt new file mode 100644 index 000000000..42b390bc9 --- /dev/null +++ b/experiments/us-childcare-attendance/review-validation-criteria.txt @@ -0,0 +1,5 @@ +Revision validation criteria set before expanded results, 2026-09-15. +Engineering (required): exact row/recipe receipt, fail on missing/tampered metadata or values; changed source/seed/policy rejected; final attendance complete and internally valid; missing household identities rejected; observed zeros preserved by primitive; original population columns/weights/period unchanged. +Statistical diagnostic screens (provisional, not release authorization): youngest-pair participation absolute gap <=5 percentage points; pair days/hour correlation absolute gap <=0.10; joint days/hour product relative gap <=20%; 3+ household mean and SD total days/hours relative gap <=20%, all-attend gap <=5 percentage points. Report sample counts, every failure and independent-draw comparison. Screens informed by previously viewed marginal diagnostics; no untouched validation claim. Failure prevents treating household process as statistically qualified; do not tune to these folds and claim new acceptance. +Transport sensitivity: preserve each child's observed regular hours. Compare donor-day baseline with (a) zero imputed irregular hours, (b) one fewer and one more attended day, bounded by observed-hours feasibility and seven days. Keep matching fields, random seed and original population fixed; retransfer altered bridge donors and measure all state CCDF outcomes. Report relative benefit changes; >10% national or >20% state with positive baseline is an unresolved sensitivity flag. These are assumption stress tests, not confidence intervals or identification of missing days. +Publication remains production_ready:false without independent held-out evidence and normal release gates. diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py index 54a59f0d0..ec7cfbaf8 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance.py @@ -112,6 +112,15 @@ def impute_us_childcare_attendance( raise ValueError("Sibling dependence must be between zero and one.") if sibling_dependence > 0: _ids(person, "childcare_source_household_id", unique=False) + if ( + person.childcare_source_household_id.str.strip() + .str.lower() + .isin(["nan", "none", ""]) + .any() + ): + raise ValueError( + "Childcare household source identities cannot be missing-value strings." + ) levels = (match_columns, *fallback_match_columns) if any( "age" not in level diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py new file mode 100644 index 000000000..6d861e764 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py @@ -0,0 +1,232 @@ +"""Content-bound attendance lineage across checkpoints, selection and native H5. + +Hashes detect changed values or execution metadata, not maliciously forged +receipts. Publication authorization and independent source review are separate. +Weights may change and whole-household exports may select/reorder people; each +retained person's identity, age, membership and attendance must remain exact. +""" + +from __future__ import annotations + +import hashlib +import json +from importlib.metadata import PackageNotFoundError, version +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + childcare_attendance_contract, +) +from microcosm.frame import Frame + +ATTENDANCE_RECEIPT_KEY = "childcare_attendance_binding" +ATTENDANCE_H5_KEY = "_childcare_attendance_receipt" +ATTENDANCE_CONTEXT_KEYS = ( + "nsece_childcare_attendance", + "childcare_predictor_harmonization", + "childcare_outside_domain_baseline", + "childcare_attendance_stage", +) + + +def _plain(value): + return json.loads(json.dumps(value, default=dict, allow_nan=False)) + + +def _digest(value): + return hashlib.sha256( + json.dumps( + value, sort_keys=True, separators=(",", ":"), allow_nan=False + ).encode() + ).hexdigest() + + +def attendance_recipe_identity(): + """Bind the packaged source contract and the code implementing its recipe.""" + directory = Path(__file__).parent + names = ( + "childcare_attendance.py", + "childcare_population.py", + "nsece_childcare.py", + "nsece_childcare_bridge.py", + "nsece_childcare_dependence.py", + "childcare_attendance_stage.py", + "childcare_attendance_receipt.py", + ) + versions = {} + for name in ("numpy", "pandas", "policyengine-us"): + try: + versions[name] = version(name) + except PackageNotFoundError: + versions[name] = None + return { + "contract_sha256": _digest(childcare_attendance_contract()), + "runtime_versions": versions, + "code_sha256": { + name: hashlib.sha256((directory / name).read_bytes()).hexdigest() + for name in names + }, + } + + +def _context(frame): + return _plain( + { + key: frame.metadata[key] + for key in ATTENDANCE_CONTEXT_KEYS + if key in frame.metadata + } + ) + + +def _row_digests(people, execution_sha256): + required = [ + "person_id", + "person_household_id", + "age", + *US_CHILDCARE_ATTENDANCE_COLUMNS, + ] + if not set(required).issubset(people): + raise ValueError( + "Attendance binding requires person IDs, household links, age and all outputs." + ) + if ( + people.reindex(columns=required[:3]).isna().any().any() + or people.person_id.duplicated().any() + ): + raise ValueError( + "Attendance binding requires unique person IDs and complete identity/age." + ) + result = {} + for row in people.reindex(columns=required).itertuples(index=False, name=None): + key = str(row[0]) + if key in result: + raise ValueError( + "Attendance person IDs have ambiguous canonical representations." + ) + result[key] = _digest( + [ + execution_sha256, + key, + str(row[1]), + *[None if pd.isna(x) else float(x) for x in row[2:]], + ] + ) + return result + + +def bind_childcare_attendance(frame): + """Seal a freshly executed source stage; never use this to bless loaded values.""" + context = _context(frame) + if "nsece_childcare_attendance" not in context: + raise ValueError("Cannot bind attendance without a source execution receipt.") + execution = {"recipe": attendance_recipe_identity(), "context": context} + execution_sha256 = _digest(execution) + binding = { + "schema_version": 1, + "execution": execution, + "execution_sha256": execution_sha256, + # Frame metadata uses small immutable mappings with linear key lookup. + # A sequence keeps freezing/serializing a population-sized inventory + # linear; construct a temporary dict only while verifying it. + "rows": list(_row_digests(frame.table("person"), execution_sha256).items()), + } + binding["binding_sha256"] = _digest(binding) + return Frame( + {e: frame.table(e) for e in frame.entities}, + frame.schema, + {e: frame.weights_for(e) for e in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={**frame.metadata, ATTENDANCE_RECEIPT_KEY: binding}, + ) + + +def assert_bound_childcare_attendance(frame, *, require_stage=True): + """Validate the exact retained values and recipe; reject stale/unbound inputs.""" + binding = _plain(frame.metadata.get(ATTENDANCE_RECEIPT_KEY, {})) + if binding.get("schema_version") != 1: + raise ValueError( + "Attendance needs a content-bound source receipt; rebuild from the original parent." + ) + claimed = binding.pop("binding_sha256", None) + if claimed != _digest(binding): + raise ValueError("Attendance receipt content hash mismatch.") + execution = binding["execution"] + if binding["execution_sha256"] != _digest(execution): + raise ValueError("Attendance execution hash mismatch.") + if execution["recipe"] != attendance_recipe_identity(): + raise ValueError("Attendance recipe changed; rebuild from the original parent.") + if execution["context"] != _context(frame): + raise ValueError("Attendance metadata disagrees with its bound execution.") + context = execution["context"] + if require_stage: + stage = context.get("childcare_attendance_stage", {}) + source = context.get("nsece_childcare_attendance", {}) + expected = [x["sha256"] for x in childcare_attendance_contract()["artifacts"]] + if ( + [x.get("sha256") for x in source.get("artifacts", [])] != expected + or stage.get("stage") != "nsece_childcare_attendance" + or stage.get("seed") != source.get("seed") + or stage.get("modeled_age_domain") != [0, 12] + or stage.get("outside_domain_policy") + not in ("require_observed", "inherit_engine_baseline") + ): + raise ValueError( + "Attendance release requires the pinned production source stage receipt." + ) + rows = dict(binding["rows"]) + if len(rows) != len(binding["rows"]): + raise ValueError("Attendance receipt contains duplicate person identities.") + for person, digest in _row_digests( + frame.table("person"), binding["execution_sha256"] + ).items(): + if rows.get(person) != digest: + raise ValueError( + "Attendance values, identities or membership differ from the source receipt." + ) + return { + "schema_version": 1, + "binding_sha256": claimed, + "execution_sha256": binding["execution_sha256"], + "source_people": len(binding["rows"]), + "retained_people": len(frame.table("person")), + "execution": execution, + } + + +def restore_native_childcare_receipt(path, frame): + """Load the receipt through either native ingress, preserving its validation.""" + with pd.HDFStore(path, mode="r") as store: + metadata = ( + json.loads(store[ATTENDANCE_H5_KEY].iloc[0]) + if ATTENDANCE_H5_KEY in store + else {} + ) + if metadata: + frame = Frame( + {e: frame.table(e) for e in frame.entities}, + frame.schema, + {e: frame.weights_for(e) for e in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={**frame.metadata, **metadata}, + ) + assert_bound_childcare_attendance(frame, require_stage=False) + else: + people = frame.table("person") + columns = [c for c in US_CHILDCARE_ATTENDANCE_COLUMNS if c in people] + if columns and np.any(people[columns].fillna(0).to_numpy(dtype=float) != 0): + raise ValueError( + "Native attendance values lack a bound receipt; rebuild from the original parent." + ) + return frame + + +def childcare_attendance_public_metadata(frame): + """Aggregate-only evidence: never publish the private person hash inventory.""" + summary = assert_bound_childcare_attendance(frame, require_stage=False) + return {**_context(frame), ATTENDANCE_RECEIPT_KEY: summary} diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py index 59b01f905..8e1dbc94a 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py @@ -21,6 +21,11 @@ US_CHILDCARE_ATTENDANCE_COLUMNS, childcare_attendance_contract, ) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + ATTENDANCE_H5_KEY, + assert_bound_childcare_attendance, + bind_childcare_attendance, +) from microcosm.build.us_runtime.childcare_population import ( harmonize_asec_childcare_predictors, ) @@ -49,6 +54,7 @@ def inherit_outside_domain_attendance_baseline(frame: Frame) -> Frame: """ from policyengine_us import CountryTaxBenefitSystem + assert_bound_childcare_attendance(frame, require_stage=False) people = frame.table("person").copy() system = CountryTaxBenefitSystem() outside = ~people.age.between(0, 12) @@ -74,21 +80,23 @@ def inherit_outside_domain_attendance_baseline(frame: Frame) -> Frame: inherited[column] = int(missing.sum()) tables = {entity: frame.table(entity) for entity in frame.entities} tables["person"] = people - return Frame( - tables, - frame.schema, - {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, - frame.strata, - mass_log=frame.mass_log, - metadata={ - **frame.metadata, - "childcare_outside_domain_baseline": { - "engine_version": version("policyengine-us"), - "values": defaults, - "inherited_counts": inherited, - "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + return bind_childcare_attendance( + Frame( + tables, + frame.schema, + {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={ + **frame.metadata, + "childcare_outside_domain_baseline": { + "engine_version": version("policyengine-us"), + "values": defaults, + "inherited_counts": inherited, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + }, }, - }, + ) ) @@ -109,6 +117,29 @@ def with_us_childcare_attendance_inputs( "Childcare source-stage outputs drifted from the engine inputs." ) source = load_nsece_childcare(household_tsv, calendar_tsv) + outside_policy = ( + "inherit_engine_baseline" + if inherit_outside_domain_baseline + else "require_observed" + ) + if "childcare_attendance_stage" in frame.metadata: + assert_bound_childcare_attendance(frame) + previous = frame.metadata["childcare_attendance_stage"] + if ( + previous["seed"] != seed + or previous["outside_domain_policy"] != outside_policy + ): + raise ValueError( + "Attendance settings changed; rebuild from the original parent." + ) + assert_childcare_attendance_exportable(frame) + return frame + people = frame.table("person") + existing = [c for c in US_CHILDCARE_ATTENDANCE_COLUMNS if c in people] + if existing and people.loc[people.age.between(0, 12), existing].notna().any().any(): + raise ValueError( + "Pre-existing child attendance lacks a production receipt; rebuild from the original parent." + ) dependence = fit_nsece_sibling_dependence(source.children) source = bridge_nsece_noncalendar_attendance(source, seed=seed) normalized = harmonize_asec_childcare_predictors( @@ -125,31 +156,54 @@ def with_us_childcare_attendance_inputs( if inherit_outside_domain_baseline: candidate = inherit_outside_domain_attendance_baseline(candidate) assert_childcare_attendance_exportable(candidate) - return Frame( - {entity: candidate.table(entity) for entity in candidate.entities}, - candidate.schema, - { - entity: candidate.weights_for(entity) - for entity in candidate.weighted_entities - }, - candidate.strata, - mass_log=candidate.mass_log, - metadata={ - **candidate.metadata, - "childcare_attendance_stage": { - "stage": spec.stage, - "outputs": spec.outputs, - "seed": seed, - "operation_order": tuple( - operation.kind for operation in spec.operations - ), - "sibling_dependence": dependence, - "modeled_age_domain": [0, 12], - "outside_domain_policy": "inherit_engine_baseline" - if inherit_outside_domain_baseline - else "require_observed", + return bind_childcare_attendance( + Frame( + {entity: candidate.table(entity) for entity in candidate.entities}, + candidate.schema, + { + entity: candidate.weights_for(entity) + for entity in candidate.weighted_entities + }, + candidate.strata, + mass_log=candidate.mass_log, + metadata={ + **candidate.metadata, + "childcare_attendance_stage": { + "stage": spec.stage, + "outputs": spec.outputs, + "seed": seed, + "operations": [ + { + "kind": spec.operations[0].kind, + **spec.operations[0].parameters, + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": dependence["rho"], + }, + { + "kind": spec.operations[1].kind, + **spec.operations[1].parameters, + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors", + }, + { + "kind": spec.operations[2].kind, + **spec.operations[2].parameters, + }, + {"kind": "export_policy", "operation": outside_policy}, + ], + "sibling_dependence": dependence, + "modeled_age_domain": [0, 12], + "outside_domain_policy": "inherit_engine_baseline" + if inherit_outside_domain_baseline + else "require_observed", + }, }, - }, + ) ) @@ -167,6 +221,7 @@ def export_native_childcare_candidate( if output_path.exists() or parent_path.resolve() == output_path.resolve(): raise ValueError("Native childcare output must be a new path.") assert_childcare_attendance_exportable(candidate) + assert_bound_childcare_attendance(candidate, require_stage=False) parent = USSingleYearDataset(file_path=str(parent_path)) attendance = set(US_CHILDCARE_ATTENDANCE_COLUMNS) for entity in candidate.entities: @@ -226,6 +281,29 @@ def _write_childcare_candidate_person_table( store, "person", people, preferred_format="table", data_columns=True ) store.put( - "_childcare_attendance_receipt", + ATTENDANCE_H5_KEY, pd.Series([json.dumps(receipt, default=dict, allow_nan=False)]), ) + + +def persist_native_childcare_receipt(path: str | Path, frame: Frame) -> dict: + """Persist final release evidence and verify the actual serialized attendance.""" + from policyengine_us.data import USSingleYearDataset + + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + restore_native_childcare_receipt, + ) + + assert_childcare_attendance_exportable(frame) + summary = assert_bound_childcare_attendance(frame) + dataset = USSingleYearDataset(file_path=str(path)) + _write_childcare_candidate_person_table(Path(path), dataset.person, frame.metadata) + # Use the written values, not the pre-export arrays, for the binding check. + dataset = USSingleYearDataset(file_path=str(path)) + tables = {e: getattr(dataset, e).copy() for e in frame.entities} + tables["household"] = tables["household"].drop(columns="household_weight") + written = Frame( + tables, frame.schema, {e: frame.weights_for(e) for e in frame.weighted_entities} + ) + restore_native_childcare_receipt(path, written) + return summary diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py index 93c0568a5..f6fd73b99 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_population.py @@ -95,9 +95,28 @@ def harmonize_asec_childcare_predictors( household = frame.table("household") if "household_source_id" in household: identities = household.set_index("household_id").household_source_id - person["childcare_source_household_id"] = person.person_household_id.map( - identities - ).astype(str) + if not identities.index.is_unique or identities.isna().any(): + raise ValueError( + "Childcare household identities must be complete and uniquely linked." + ) + mapped = person.person_household_id.map(identities) + if mapped.isna().any(): + raise ValueError("Childcare person-to-household link does not resolve.") + if not mapped.map( + lambda value: ( + ( + isinstance(value, str) + and value.strip().lower() not in ("", "nan", "none", "") + ) + or ( + isinstance(value, (int, np.integer)) and not isinstance(value, bool) + ) + ) + ).all(): + raise ValueError( + "Childcare household identities must be canonical strings or integers." + ) + person["childcare_source_household_id"] = mapped.astype(str) contract = childcare_attendance_contract() prices = { int(year): value for year, value in contract["cpi_u_annual_average"].items() diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py new file mode 100644 index 000000000..de7a0f347 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py @@ -0,0 +1,108 @@ +"""Assumption stress tests for noncalendar schedules; never source observations.""" + +from __future__ import annotations + +import gc + +import numpy as np + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.nsece_childcare import NSECEChildcareSource +from microcosm.calibrate.geography_constants import US_STATE_NUMERIC_FIPS_TO_POSTAL + + +def noncalendar_sensitivity_source(source, *, irregular_hours=True, day_shift=0): + """Change only modeled bridge components, keeping measured regular hours. + + The selected donor's days shift by one, bounded by 1--7 and the minimum + feasible days at 24 hours/day. Zero total hours always means zero days. + These brackets are stress tests, not confidence intervals. + """ + if day_shift not in (-1, 0, 1): + raise ValueError("The sensitivity day shift must be -1, 0 or 1.") + children = source.children.copy() + rows = children.attendance_status.eq("summary_bridge") + month, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS + regular = children.loc[rows, "regular_hours_per_week"].to_numpy(dtype=float) + irregular = children.loc[rows, "irregular_hours_per_week"].to_numpy(dtype=float) + if not irregular_hours: + irregular = np.zeros_like(irregular) + total = regular + irregular + attended = np.clip(children.loc[rows, days].to_numpy(dtype=float) + day_shift, 1, 7) + attended = np.where(total > 0, np.maximum(attended, np.ceil(total / 24)), 0) + children.loc[rows, days] = attended + children.loc[rows, month] = np.floor(attended * 52 / 12 + 0.5) + children.loc[rows, hours] = np.divide( + total, attended, out=np.zeros_like(total), where=attended > 0 + ) + children.loc[rows, "ece_hours_per_week"] = total + children.loc[rows, "irregular_hours_per_week"] = irregular + return NSECEChildcareSource( + children, + source.weights, + { + **source.source_receipt, + "transport_sensitivity": { + "include_modeled_irregular_hours": irregular_hours, + "modeled_day_shift": day_shift, + "measured_regular_hours_preserved": True, + }, + }, + ) + + +def compare_childcare_scenarios(parent, scenarios, *, year=2026): + """Yield state comparisons on one fixed population; vary only attendance.""" + from policyengine_us import Microsimulation + from policyengine_us.data import USSingleYearDataset + + people = parent.table("person") + young = people.age.between(0, 12).to_numpy() + for name, values in scenarios.items(): + if np.shape(values) != (len(people), 3) or not np.isfinite(values[young]).all(): + raise ValueError(f"Invalid scenario attendance: {name}") + households = parent.table("household").copy() + households["household_weight"] = parent.weights_for("household").values + for fips, group in households.groupby("state_fips", sort=True): + state = US_STATE_NUMERIC_FIPS_TO_POSTAL[int(fips)] + selected = people.person_household_id.isin(group.household_id) + state_people = people.loc[selected].copy() + tables = {"person": state_people, "household": group} + for entity in parent.schema.group_entities: + if entity != "household": + table = parent.table(entity) + tables[entity] = table.loc[ + table[f"{entity}_id"].isin(state_people[f"person_{entity}_id"]) + ] + result = {"state": state, "sample_households": len(group)} + for name, attendance in {"baseline": None, **scenarios}.items(): + sim = Microsimulation( + dataset=USSingleYearDataset(**tables, time_period=year) + ) + if attendance is not None: + for i, column in enumerate(US_CHILDCARE_ATTENDANCE_COLUMNS): + values = np.asarray(sim.calculate(column, year)).copy() + specified = attendance[selected, i] + known = np.isfinite(specified) + values[known] = specified[known] + sim.set_input(column, year, values) + variable = f"{state.lower()}_child_care_subsidies" + definition = sim.tax_benefit_system.variables[variable] + amount = np.asarray(sim.calculate(variable, year), dtype=float) + weights = np.asarray( + sim.calculate(f"{definition.entity.key}_weight", year), dtype=float + ) + if not np.isfinite(amount).all(): + raise ValueError(f"Nonfinite state benefit: {state}/{name}") + result[name] = { + "variable": variable, + "entity": definition.entity.key, + "positive_sample_units": int((amount > 0).sum()), + "weighted_positive_units": float(weights[amount > 0].sum()), + "annual_modeled_benefits": float(amount @ weights), + } + del sim + gc.collect() + yield result diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/h5_io.py b/packages/microcosm-build/src/microcosm/build/us_runtime/h5_io.py index 48b87cb2b..b7b0d8f96 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/h5_io.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/h5_io.py @@ -948,6 +948,12 @@ def load_legacy_calibrated_us_h5(path: str | Path) -> Frame: }, ) assert_h5_unchanged(path, sha256, consumer=consumer) + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + restore_native_childcare_receipt, + ) + + frame = restore_native_childcare_receipt(path, frame) + assert_h5_unchanged(path, sha256, consumer=consumer) refuse_denied_frame(frame, consumer=consumer) return canonicalize_frame_string_dtypes( frame, diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py b/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py index 7d31c47e0..0aa782794 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py @@ -519,6 +519,12 @@ def load_us_frame(path: str | Path) -> Frame: US_SCHEMA, {"household": Weights(weights, WeightKind.CALIBRATED)}, ) + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + restore_native_childcare_receipt, + ) + + frame = restore_native_childcare_receipt(path, frame) + assert_h5_unchanged(path, sha256, consumer=consumer) refuse_denied_frame(frame, consumer=consumer) return frame diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py index 960e13385..9e7f6c4b2 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py @@ -9,6 +9,7 @@ from __future__ import annotations import hashlib +import json from dataclasses import dataclass from pathlib import Path @@ -302,8 +303,45 @@ def with_us_nsece_childcare_attendance( """ if frame.schema != US_SCHEMA: raise ValueError("NSECE childcare attendance requires the US schema.") + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, + bind_childcare_attendance, + ) + + receipt = { + **source.source_receipt, + "seed": int(seed), + "match_columns": match_columns, + "fallback_match_columns": fallback_match_columns, + "sibling_dependence": sibling_dependence, + "candidate_only": True, + } + if "nsece_childcare_attendance" in frame.metadata: + assert_bound_childcare_attendance(frame, require_stage=False) + previous = json.loads( + json.dumps(frame.metadata["nsece_childcare_attendance"], default=dict) + ) + if previous != json.loads(json.dumps(receipt)): + raise ValueError( + "Attendance source or settings changed; rebuild from the original parent." + ) + return frame donor, weights = source.donors() original_people = frame.table("person") + for column in US_CHILDCARE_ATTENDANCE_COLUMNS: + provenance = f"{column}_source" + if ( + provenance in original_people + and original_people[provenance] + .astype("string") + .str.startswith( + ("donor:", "source_person:", "inherited_engine_baseline"), na=False + ) + .any() + ): + raise ValueError( + "Imputed attendance lost its receipt; rebuild from the original parent." + ) recipients = original_people.copy() # Native BuildP IDs are exact int64, while the pure donor API uses strings. # Encode integers losslessly for hashing, then restore the native column. @@ -329,23 +367,15 @@ def with_us_nsece_childcare_attendance( people["person_source_id"] = original_people.person_source_id tables = {entity: frame.table(entity).copy() for entity in frame.entities} tables["person"] = people - return Frame( - tables, - frame.schema, - {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, - frame.strata, - mass_log=frame.mass_log, - metadata={ - **frame.metadata, - "nsece_childcare_attendance": { - **source.source_receipt, - "seed": int(seed), - "match_columns": match_columns, - "fallback_match_columns": fallback_match_columns, - "sibling_dependence": sibling_dependence, - "candidate_only": True, - }, - }, + return bind_childcare_attendance( + Frame( + tables, + frame.schema, + {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata={**frame.metadata, "nsece_childcare_attendance": receipt}, + ) ) @@ -365,6 +395,11 @@ def assert_childcare_attendance_exportable(frame: Frame) -> None: raise ValueError( "Childcare export has unresolved attendance; do not fill with zero." ) + # Completeness alone is insufficient: preserve the joint nonattendance, + # calendar bounds and integral-month contract at the final boundary too. + from microcosm.build.us_runtime.childcare_attendance import _validate_attendance + + _validate_attendance(people, complete=True) def nsece_childcare_validation_report( diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py new file mode 100644 index 000000000..e5afb5ee1 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py @@ -0,0 +1,285 @@ +"""Held-out joint schedule moments for the actual household rank mixture. + +Integrate the finite donor CDFs exactly, rather than selecting a favorable +simulation seed. Whole households are held out, including all larger sibships. +""" + +from __future__ import annotations + +import hashlib +from itertools import combinations + +import numpy as np +import pandas as pd + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, + NSECE_CHILDCARE_MATCH_COLUMNS, +) +from microcosm.build.us_runtime.nsece_childcare_dependence import ( + fit_nsece_sibling_dependence, +) + + +def _joint_product( + first: tuple[np.ndarray, np.ndarray], second: tuple[np.ndarray, np.ndarray] +): + """E[XY] under one shared uniform, with arbitrary weighted discrete CDFs.""" + a, pa = first + b, pb = second + cuts = np.unique(np.r_[0, pa, pb, 1]) + midpoints = (cuts[:-1] + cuts[1:]) / 2 + x = a[np.minimum(np.searchsorted(pa, midpoints, side="right"), len(a) - 1)] + y = b[np.minimum(np.searchsorted(pb, midpoints, side="right"), len(b) - 1)] + return np.sum(x * y * np.diff(cuts)[:, None], axis=0) + + +def _moments(values, weights): + return np.average(values, weights=weights, axis=0) + + +def _pair_summary(records): + weights = np.array([r[0] for r in records]) + result = {} + for arm, index in (("observed", 1), ("independent", 2), ("coupled", 3)): + moments = _moments(np.array([r[index] for r in records]), weights) + metrics = {} + for j, name in enumerate(("participation", "days", "weekly_hours")): + x, y, xx, yy, xy = moments[:, j] + denominator = np.sqrt(max(0, xx - x * x) * max(0, yy - y * y)) + metrics[name] = { + "joint_product": float(xy), + "correlation": float((xy - x * y) / denominator) + if denominator > 0 + else None, + } + result[arm] = metrics + return { + "households": len(records), + "household_weight": float(weights.sum()), + **result, + } + + +def assess_sibling_schedules(source, *, seed=271828): + """Evaluate youngest-pair days/hours and all-child totals for 3+ households.""" + children = source.children.loc[source.children.age.between(0, 12)].copy() + match_columns = NSECE_CHILDCARE_MATCH_COLUMNS + levels = (match_columns, *NSECE_CHILDCARE_FALLBACK_COLUMNS) + folds = children.source_household_id.map( + lambda x: ( + int.from_bytes(hashlib.sha256(f"{seed}:{x}".encode()).digest()[:8], "big") + % 5 + ) + ) + pairs, large_pairs, larger = [], [], [] + month, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS + del month + fold_fits = [] + for fold in range(5): + training = children.loc[folds != fold] + rho = fit_nsece_sibling_dependence(training)["rho"] + fold_fits.append(rho) + train = training.loc[training.attendance_status.eq("complete")].sort_values( + [days, hours, "donor_id"] + ) + groups = [ + (level, train.groupby(list(level), sort=False).indices) for level in levels + ] + cache = {} + + def distribution( + child: pd.Series, + cache: dict = cache, + groups: list = groups, + train: pd.DataFrame = train, + ): + key = tuple(child.reindex(match_columns)) + if key not in cache: + for level, indices in groups: + k = tuple(child.reindex(level)) + pool = train.iloc[indices.get(k[0] if len(k) == 1 else k, [])] + pool = pool.loc[pool.child_weight > 0] + if len(pool): + break + if pool.empty: + raise ValueError("Unsupported held-out sibling matching cell.") + probabilities = pool.child_weight.to_numpy(dtype=float, copy=True) + probabilities /= probabilities.sum() + values = np.column_stack( + (pool[days] > 0, pool[days], pool[days] * pool[hours]) + ).astype(float) + cumulative = probabilities.cumsum() + cumulative[-1] = 1 + cache[key] = (values, cumulative, probabilities) + return cache[key] + + for _, household in children.loc[folds == fold].groupby("source_household_id"): + if ( + len(household) < 2 + or not household.attendance_status.eq("complete").all() + ): + continue + household = household.sort_values(["age", "donor_id"]) + w = household.household_weight.to_numpy(dtype=float) + if not np.isfinite(w).all() or (w <= 0).any() or not (w == w[0]).all(): + raise ValueError( + "Sibling assessment requires consistent household design weights." + ) + distributions = [distribution(row) for _, row in household.iterrows()] + means = np.array([_moments(d[0], d[2]) for d in distributions]) + seconds = np.array([_moments(d[0] ** 2, d[2]) for d in distributions]) + actual = np.column_stack( + ( + household[days] > 0, + household[days], + household[days] * household[hours], + ) + ).astype(float) + joint = _joint_product(distributions[0][:2], distributions[1][:2]) + independent = means[0] * means[1] + observed = np.array( + [ + actual[0], + actual[1], + actual[0] ** 2, + actual[1] ** 2, + actual[0] * actual[1], + ] + ) + uncoupled = np.array( + [means[0], means[1], seconds[0], seconds[1], independent] + ) + coupled = np.array( + [ + means[0], + means[1], + seconds[0], + seconds[1], + (1 - rho) * independent + rho * joint, + ] + ) + record = (w[0], observed, uncoupled, coupled) + pairs.append(record) + if len(household) < 3: + continue + large_pairs.append(record) + sums = means.sum(axis=0) + sums_squared = { + "independent": seconds.sum(axis=0).copy(), + "coupled": seconds.sum(axis=0).copy(), + } + for i, j in combinations(range(len(household)), 2): + independent = means[i] * means[j] + joint = _joint_product(distributions[i][:2], distributions[j][:2]) + sums_squared["independent"] += 2 * independent + sums_squared["coupled"] += 2 * ((1 - rho) * independent + rho * joint) + independent_all = means[:, 0].prod() + larger.append( + ( + w[0], + { + "observed": np.r_[ + actual.sum(axis=0), + actual.sum(axis=0) ** 2, + actual[:, 0].prod(), + ], + "independent": np.r_[ + sums, sums_squared["independent"], independent_all + ], + "coupled": np.r_[ + sums, + sums_squared["coupled"], + (1 - rho) * independent_all + rho * means[:, 0].min(), + ], + }, + ) + ) + result = { + "design": "five household-separated folds; exact integration of the implemented shared-rank donor CDFs", + "seed": seed, + "match_columns": match_columns, + "training_rho": fold_fits, + "youngest_pairs": _pair_summary(pairs) if pairs else None, + "youngest_pairs_in_3plus_households": _pair_summary(large_pairs) + if large_pairs + else None, + "larger_households": {"households": len(larger)}, + "production_ready": False, + } + if larger: + weights = np.array([r[0] for r in larger]) + for arm in ("observed", "independent", "coupled"): + values = _moments(np.array([r[1][arm] for r in larger]), weights) + result["larger_households"][arm] = { + "mean_total_days": float(values[1]), + "mean_total_weekly_hours": float(values[2]), + "sd_total_days": float(np.sqrt(max(0, values[4] - values[1] ** 2))), + "sd_total_weekly_hours": float( + np.sqrt(max(0, values[5] - values[2] ** 2)) + ), + "all_children_attend": float(values[6]), + } + result["diagnostic_screen"] = sibling_schedule_screen(result) + return result + + +def sibling_schedule_screen(result): + """Developmental screens declared before this expanded assessment was run. + + Passing would not certify the survey transport or authorize publication. + Undefined metrics and empty subgroups are failures, not silent passes. + """ + checks = [] + + def check(name, observed, predicted, limit, relative=False): + gap = None + if observed is not None and predicted is not None: + if not relative or observed != 0: + gap = abs(predicted - observed) / (abs(observed) if relative else 1) + checks.append( + { + "metric": name, + "absolute_gap": gap, + "relative": relative, + "limit": limit, + "passed": gap is not None and gap <= limit, + } + ) + + for name in ("youngest_pairs", "youngest_pairs_in_3plus_households"): + values = result.get(name) or {} + for metric, moment, limit, relative in ( + ("participation", "joint_product", 0.05, False), + ("days", "correlation", 0.10, False), + ("weekly_hours", "correlation", 0.10, False), + ("days", "joint_product", 0.20, True), + ("weekly_hours", "joint_product", 0.20, True), + ): + observed = values.get("observed", {}).get(metric, {}).get(moment) + predicted = values.get("coupled", {}).get(metric, {}).get(moment) + check(f"{name}.{metric}.{moment}", observed, predicted, limit, relative) + larger = result["larger_households"] + for metric in ( + "mean_total_days", + "mean_total_weekly_hours", + "sd_total_days", + "sd_total_weekly_hours", + "all_children_attend", + ): + relative = metric != "all_children_attend" + check( + f"larger_households.{metric}", + larger.get("observed", {}).get(metric), + larger.get("coupled", {}).get(metric), + 0.20 if relative else 0.05, + relative, + ) + return { + "passed": all(c["passed"] for c in checks), + "checks": checks, + "interpretation": "provisional diagnostic screens; not publication authorization", + } diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py b/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py index 94d358338..104e8309d 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/release_input_coverage.py @@ -602,7 +602,22 @@ def us_release_input_coverage_gate( present_values["household_weight"] = frame.weights_for("household").values degenerate, no_observed = _degenerate_columns(present_values, engine) - return input_column_coverage_gate( + attendance_details = None + attendance_failures = () + if set(US_CHILDCARE_ATTENDANCE_COLUMNS) & (required | set(present_values)): + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, + ) + from microcosm.build.us_runtime.nsece_childcare import ( + assert_childcare_attendance_exportable, + ) + + try: + assert_childcare_attendance_exportable(frame) + attendance_details = assert_bound_childcare_attendance(frame) + except ValueError as error: + attendance_failures = (str(error),) + result = input_column_coverage_gate( present_values.keys(), required_columns=required, degenerate_columns=degenerate, @@ -610,6 +625,12 @@ def us_release_input_coverage_gate( reviewed_exclusions=reviewed, name="us_release_input_coverage", ) + return GateResult( + name=result.name, + passed=result.passed and not attendance_failures, + failures=(*result.failures, *attendance_failures), + details={**result.details, "childcare_attendance": attendance_details}, + ) def _coverage_engine() -> Any | None: diff --git a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py index 04ddb3149..a42ebd607 100644 --- a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py +++ b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py @@ -12311,3 +12311,34 @@ def test_attendance_source_cli_requires_paired_inputs(options): builder._parse_args( ["--ledger-facts", "facts.jsonl", "--out", "release", *options] ) + + +def test_attendance_integrity_is_unconditional_at_final_native_write(): + """Neither coverage overrides nor omitted TSV flags can skip integrity. + + Pair this ordering contract with the behavioral receipt, row validation, + and real native reload tests in test_us_nsece_childcare.py. + """ + import ast + + main = ast.parse(inspect.getsource(_load_builder_module()._main)).body[0] + calls = [] + # Direct body statements prove these checks are outside optional branches. + for statement in main.body: + value = getattr(statement, "value", None) + if isinstance(value, ast.Call): + function = value.func + name = ( + function.attr + if isinstance(function, ast.Attribute) + else getattr(function, "id", None) + ) + calls.append(name) + expected = [ + "assert_childcare_attendance_exportable", + "assert_bound_childcare_attendance", + "write_dataset", + "persist_native_childcare_receipt", + ] + positions = [calls.index(name) for name in expected] + assert positions == list(range(positions[0], positions[0] + 4)) diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index 812470a04..22e1b1636 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -1,6 +1,7 @@ """Synthetic source-code and Frame/export contracts; no NSECE microdata in CI.""" import json +from types import SimpleNamespace import numpy as np import pandas as pd @@ -11,9 +12,17 @@ load_frame_checkpoint, write_frame_checkpoint, ) +from microcosm.build.us_runtime import childcare_attendance_stage as stage from microcosm.build.us_runtime.childcare_attendance import ( US_CHILDCARE_ATTENDANCE_COLUMNS, ) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, + restore_native_childcare_receipt, +) +from microcosm.build.us_runtime.childcare_population import ( + harmonize_asec_childcare_predictors, +) from microcosm.build.us_runtime.nsece_childcare import ( NSECE_CALENDAR_BLOCKS, NSECE_CHILD_INDICES, @@ -26,6 +35,11 @@ nsece_childcare_validation_report, with_us_nsece_childcare_attendance, ) +from microcosm.build.us_runtime.release_input_coverage import ( + ReleaseInputColumn, + ReleaseInputCoverageManifest, + us_release_input_coverage_gate, +) from microcosm.frame import US_SCHEMA, Frame, WeightKind, Weights MONTH, DAYS, HOURS = US_CHILDCARE_ATTENDANCE_COLUMNS @@ -591,7 +605,7 @@ def test_asec_income_sidecar_checks_identity_and_preserves_raw_missingness( @pytest.mark.requires_us -def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch): +def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch, tmp_path): from microcosm.build.us_runtime import childcare_attendance_stage as stage frame = _asec_frame() @@ -603,6 +617,9 @@ def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch): hh["HH4_ECON_INCOME_ANNUAL"] = 40000 _care(cal, hours=3) source = derive_nsece_childcare(hh, cal) + source.source_receipt["artifacts"] = stage.childcare_attendance_contract()[ + "artifacts" + ] monkeypatch.setattr(stage, "load_nsece_childcare", lambda *args: source) result = stage.with_us_childcare_attendance_inputs( frame, @@ -622,6 +639,39 @@ def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch): np.testing.assert_array_equal( result.weights_for("household").values, frame.weights_for("household").values ) + assert_bound_childcare_attendance(result) + options = dict( + household_tsv="synthetic", + calendar_tsv="synthetic", + asec_source_cache=None, + seed=915, + inherit_outside_domain_baseline=True, + ) + assert stage.with_us_childcare_attendance_inputs(result, **options) is result + for changed in ({"seed": 916}, {"inherit_outside_domain_baseline": False}): + with pytest.raises(ValueError, match="settings changed"): + stage.with_us_childcare_attendance_inputs(result, **{**options, **changed}) + operations = result.metadata["childcare_attendance_stage"]["operations"] + assert [op["operation"] for op in operations] == [ + "calendar_attendance", + "fit_sibling_dependence", + "regular_hours_schedule_bridge", + "harmonize_asec_childcare_predictors", + "joint_weighted_schedule_transfer", + "inherit_engine_baseline", + ] + from policyengine_us.data import USSingleYearDataset + + from microcosm.build.us_runtime.l0_refit_export import load_us_frame + + tables = {e: result.table(e).copy() for e in result.entities} + tables["household"]["household_weight"] = result.weights_for("household").values + path = tmp_path / "final.h5" + USSingleYearDataset(**tables, time_period=2026).save(str(path)) + evidence = stage.persist_native_childcare_receipt(path, result) + assert evidence["retained_people"] == 2 + assert "rows" not in evidence + assert_bound_childcare_attendance(load_us_frame(path)) def test_bridge_retains_all_equally_near_donors(): @@ -648,3 +698,288 @@ def test_bridge_retains_all_equally_near_donors(): ] assert len(bridged) == 2 assert bridged.irregular_hours_per_week.eq(2).all() + + +def _replace(frame, *, people=None, metadata=None): + tables = {e: frame.table(e).copy() for e in frame.entities} + if people is not None: + tables["person"] = people + return Frame( + tables, + frame.schema, + {e: frame.weights_for(e) for e in frame.weighted_entities}, + metadata=frame.metadata if metadata is None else metadata, + ) + + +def _candidate(): + return with_us_nsece_childcare_attendance( + _frame(), _source(), seed=915, match_columns=("age",) + ) + + +@pytest.mark.parametrize( + "column,value", + [(DAYS, 4), ("age", 4), ("person_id", 99), ("person_household_id", 99)], +) +def test_persisted_value_or_identity_change_fails_binding(column, value): + candidate = _candidate() + candidate.table("person").loc[1, column] = value + with pytest.raises(ValueError, match="differ from the source receipt"): + assert_bound_childcare_attendance(candidate, require_stage=False) + + +def test_receipt_loss_and_metadata_relabeling_are_rejected(): + candidate = _candidate() + with pytest.raises(ValueError, match="content-bound"): + assert_bound_childcare_attendance( + _replace(candidate, metadata={}), require_stage=False + ) + metadata = json.loads(json.dumps(candidate.metadata, default=dict)) + metadata["nsece_childcare_attendance"]["seed"] += 1 + with pytest.raises(ValueError, match="metadata disagrees"): + assert_bound_childcare_attendance( + _replace(candidate, metadata=metadata), require_stage=False + ) + + +def test_same_transfer_is_idempotent_but_seed_and_source_refresh_are_rejected(): + source = _source() + candidate = _candidate() + assert ( + with_us_nsece_childcare_attendance( + candidate, source, seed=915, match_columns=("age",) + ) + is candidate + ) + with pytest.raises(ValueError, match="settings changed"): + with_us_nsece_childcare_attendance( + candidate, source, seed=916, match_columns=("age",) + ) + source.source_receipt["source_year"] = 2025 + with pytest.raises(ValueError, match="source or settings changed"): + with_us_nsece_childcare_attendance( + candidate, source, seed=915, match_columns=("age",) + ) + + +def test_selection_order_and_calibration_do_not_invalidate_attendance(): + candidate = _candidate() + selected = _replace(candidate, people=candidate.table("person").iloc[::-1]) + assert_bound_childcare_attendance(selected, require_stage=False) + tables = {e: selected.table(e) for e in selected.entities} + calibrated = Frame( + tables, + selected.schema, + {"household": Weights(np.array([200.0]), WeightKind.CALIBRATED)}, + metadata=selected.metadata, + ) + assert_bound_childcare_attendance(calibrated, require_stage=False) + + +@pytest.mark.parametrize("value", [np.nan, None, "", "nan", ""]) +def test_invalid_household_source_identity_fails_before_string_conversion(value): + frame = _asec_frame() + frame.table("household")["household_source_id"] = value + with pytest.raises(ValueError, match="household identities"): + harmonize_asec_childcare_predictors(frame) + + +def test_unresolved_household_membership_is_rejected(): + frame = _asec_frame() + frame.table("household")["household_source_id"] = "household" + frame.table("person")["PEPAR1"] = -1 + frame.table("person").loc[1, "person_household_id"] = 999 + with pytest.raises(ValueError, match="link does not resolve"): + harmonize_asec_childcare_predictors(frame) + + +@pytest.mark.parametrize( + "column,value", + [(DAYS, np.nan), (DAYS, 8), (DAYS, 0), (US_CHILDCARE_ATTENDANCE_COLUMNS[0], 2.5)], +) +def test_final_boundary_rechecks_each_row_and_schedule(column, value): + candidate = _candidate() + candidate.table("person").loc[1, column] = value + with pytest.raises(ValueError): + assert_childcare_attendance_exportable(candidate) + manifest = ReleaseInputCoverageManifest( + reference={}, + columns=tuple( + ReleaseInputColumn(c, "required") for c in US_CHILDCARE_ATTENDANCE_COLUMNS + ), + ) + gate = us_release_input_coverage_gate( + candidate, + SimpleNamespace(default_values=lambda columns: {c: 0 for c in columns}), + manifest=manifest, + ) + assert not gate.passed + assert any("Childcare" in x or "Attendance" in x for x in gate.failures) + + +@pytest.mark.requires_us +def test_native_reload_preserves_binding_and_detects_tampering(tmp_path): + from policyengine_us.data import USSingleYearDataset + + from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 + from microcosm.build.us_runtime.l0_refit_export import load_us_frame + + candidate = _candidate() + tables = {e: candidate.table(e).copy() for e in candidate.entities} + tables["household"]["household_weight"] = candidate.weights_for("household").values + # Native exports intentionally omit per-cell provenance columns. + tables["person"] = tables["person"].drop( + columns=[f"{c}_source" for c in US_CHILDCARE_ATTENDANCE_COLUMNS] + ) + path = tmp_path / "native.h5" + USSingleYearDataset(**tables, time_period=2026).save(str(path)) + for loader in (load_legacy_calibrated_us_h5, load_us_frame): + with pytest.raises(ValueError, match="lack a bound receipt"): + loader(path) + stage._write_childcare_candidate_person_table( + path, tables["person"], candidate.metadata + ) + for loader in (load_legacy_calibrated_us_h5, load_us_frame): + loaded = loader(path) + assert_bound_childcare_attendance(loaded, require_stage=False) + tables["person"].loc[1, DAYS] = 4 + stage._write_childcare_candidate_person_table( + path, tables["person"], candidate.metadata + ) + with pytest.raises(ValueError, match="differ from the source receipt"): + restore_native_childcare_receipt( + path, _replace(candidate, people=tables["person"], metadata={}) + ) + + +@pytest.mark.requires_us +def test_production_stage_refuses_unbound_existing_values(monkeypatch): + monkeypatch.setattr(stage, "load_nsece_childcare", lambda *args: _source()) + frame = _asec_frame() + frame.table("person").loc[1, DAYS] = 5 + with pytest.raises(ValueError, match="Pre-existing child attendance"): + stage.with_us_childcare_attendance_inputs( + frame, + household_tsv="fake", + calendar_tsv="fake", + asec_source_cache=None, + seed=915, + ) + + +def test_final_gate_cannot_accept_unbound_nondegenerate_columns(): + candidate = _replace(_candidate(), metadata={}) + manifest = ReleaseInputCoverageManifest( + reference={}, + columns=tuple( + ReleaseInputColumn(c, "required") for c in US_CHILDCARE_ATTENDANCE_COLUMNS + ), + ) + gate = us_release_input_coverage_gate( + candidate, + SimpleNamespace(default_values=lambda columns: {c: 0 for c in columns}), + manifest=manifest, + ) + assert not gate.passed + assert any("content-bound" in x for x in gate.failures) + + +def test_receipt_loss_cannot_relabel_derived_values_as_observations(): + with pytest.raises(ValueError, match="lost its receipt"): + with_us_nsece_childcare_attendance( + _replace(_candidate(), metadata={}), + _source(), + seed=916, + match_columns=("age",), + ) + + +def test_changed_recipe_invalidates_receipt(monkeypatch): + from microcosm.build.us_runtime import childcare_attendance_receipt as receipts + + candidate = _candidate() + monkeypatch.setattr( + receipts, "attendance_recipe_identity", lambda: {"different": True} + ) + with pytest.raises(ValueError, match="recipe changed"): + assert_bound_childcare_attendance(candidate, require_stage=False) + + +def test_public_metadata_omits_person_hash_inventory(): + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + ATTENDANCE_RECEIPT_KEY, + childcare_attendance_public_metadata, + ) + + candidate = _candidate() + public = childcare_attendance_public_metadata(candidate) + assert "rows" not in public[ATTENDANCE_RECEIPT_KEY] + assert public[ATTENDANCE_RECEIPT_KEY]["source_people"] == 2 + assert len(candidate.metadata[ATTENDANCE_RECEIPT_KEY]["rows"]) == 2 + # Population-sized mappings make immutable Frame metadata serialization + # quadratic. Keep the private row inventory as a sequence of ID/hash pairs. + assert isinstance(candidate.metadata[ATTENDANCE_RECEIPT_KEY]["rows"], tuple) + + +def test_retaining_a_subset_keeps_attendance_binding(): + candidate = _candidate() + selected = _replace(candidate, people=candidate.table("person").iloc[[1]]) + summary = assert_bound_childcare_attendance(selected, require_stage=False) + assert summary["source_people"] == 2 + assert summary["retained_people"] == 1 + + +def test_shared_rank_joint_moment_integrates_unequal_cdfs(): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + _joint_product, + ) + + a = (np.array([[0.0], [2.0]]), np.array([0.25, 1])) + b = (np.array([[1.0], [3.0]]), np.array([0.5, 1])) + # [0,.25): 0; [.25,.5): 2; [.5,1): 6. + assert _joint_product(a, b) == pytest.approx([3.5]) + + +def test_undefined_sibling_metrics_do_not_pass_validation(): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + sibling_schedule_screen, + ) + + result = sibling_schedule_screen({"larger_households": {}}) + assert not result["passed"] + assert all(not check["passed"] for check in result["checks"]) + + +@pytest.mark.parametrize( + "irregular,shift,expected_days,expected_hours", + [ + (False, 0, 5, 12), + (True, -1, 4, 13), + (True, 1, 6, 13), + ], +) +def test_schedule_sensitivity_preserves_measured_regular_hours( + irregular, shift, expected_days, expected_hours +): + from microcosm.build.us_runtime.childcare_sensitivity import ( + noncalendar_sensitivity_source, + ) + + source = _source() + source.children["attendance_status"] = "summary_bridge" + source.children["regular_hours_per_week"] = 12.0 + source.children["irregular_hours_per_week"] = 1.0 + original = source.children.copy(deep=True) + changed = noncalendar_sensitivity_source( + source, irregular_hours=irregular, day_shift=shift + ) + row = changed.children.iloc[0] + assert row.regular_hours_per_week == 12 + assert row[DAYS] == expected_days + assert row[DAYS] * row[HOURS] == pytest.approx(expected_hours) + assert_frame_equal(source.children, original) + observed = noncalendar_sensitivity_source( + _source(), irregular_hours=False, day_shift=-1 + ) + assert_frame_equal(observed.children, _source().children) diff --git a/packages/microcosm-build/tests/test_us_spine_blindness.py b/packages/microcosm-build/tests/test_us_spine_blindness.py index dbe124cd2..234be6951 100644 --- a/packages/microcosm-build/tests/test_us_spine_blindness.py +++ b/packages/microcosm-build/tests/test_us_spine_blindness.py @@ -234,8 +234,10 @@ # Opt-in normalized-donor preparation; not a registered build stage. # Still scanned for source-spine access by the all-runtime guard. "childcare_attendance.py", + "childcare_attendance_receipt.py", # Artifact lineage and integrity; no treatment. "childcare_attendance_stage.py", # Licensed source extension after relationship/hours inputs. "childcare_population.py", # ASEC relationship harmonization for candidate builds. + "childcare_sensitivity.py", # Local assumption and benefit diagnostics. "congressional_district_geography.py", "congressional_district_vintage.py", "congressional_district_vintage_crosswalk.py", @@ -259,6 +261,7 @@ # Hash-verified local NSECE source adapter and opt-in candidate Frame step. "nsece_childcare.py", "nsece_childcare_assessment.py", # Source selection and household validation. + "nsece_childcare_sibling_validation.py", # Whole-household schedule diagnostics. "nsece_childcare_dependence.py", # Household dependence estimation. "nsece_childcare_bridge.py", # Source measurement completion; no spine routing. "operator_boundary.py", # Raw-stage validator; no population treatment. @@ -3416,8 +3419,10 @@ def test_pool_build_tool_import_graph_is_source_spine_blind() -> None: for tool in _SPINE_BLIND_BUILD_TOOLS: runtime_graph, missing_modules = _us_runtime_import_graph(tool) - assert len(runtime_graph) == 70, ( - f"{tool.name} must reach the pinned 70-module runtime graph; " + # Native ingress additionally verifies attendance receipts and their + # pure attendance contract; both remain subject to the guard below. + assert len(runtime_graph) == 72, ( + f"{tool.name} must reach the pinned 72-module runtime graph; " f"reached {len(runtime_graph)}" ) assert not missing_modules, ( diff --git a/tools/build_us_fiscal_refresh_release.py b/tools/build_us_fiscal_refresh_release.py index 2389807c5..5554e7ab6 100644 --- a/tools/build_us_fiscal_refresh_release.py +++ b/tools/build_us_fiscal_refresh_release.py @@ -11670,7 +11670,23 @@ def _main(argv: Sequence[str] | None = None) -> None: # the batched pre-export raise so a gate-failed run never produces it. # microcosm#443: #437 dropped this call while inserting the batched raise, # so attempts 13/14 smoke-scored a stale artifact from a prior run. + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, + ) + from microcosm.build.us_runtime.childcare_attendance_stage import ( + persist_native_childcare_receipt, + ) + from microcosm.build.us_runtime.nsece_childcare import ( + assert_childcare_attendance_exportable, + ) + + # Attendance integrity is not waivable by generic coverage/evidence flags. + assert_childcare_attendance_exportable(export_frame) + assert_bound_childcare_attendance(export_frame) release_engine.write_dataset(export_frame, dataset_path, period=PERIOD) + attendance_source_evidence = persist_native_childcare_receipt( + dataset_path, export_frame + ) # microcosm#368: reform-coverage smoke on the WRITTEN release H5. The column # gate above proves the required keys exist and carry signal; this is the # end-to-end backstop: each pinned probe (first: SSI asset limits at @@ -11798,18 +11814,7 @@ def _main(argv: Sequence[str] | None = None) -> None: reviewed_exclusions=_reviewed_exclusions(active_aliases), ) coverage["fiscal_target_sources"] = _fiscal_target_source_provenance(target_specs) - attendance_receipts = { - key: json.loads(json.dumps(base_frame.metadata[key], default=dict)) - for key in ( - "childcare_attendance_stage", - "nsece_childcare_attendance", - "childcare_predictor_harmonization", - "childcare_outside_domain_baseline", - ) - if key in base_frame.metadata - } - if attendance_receipts: - coverage["childcare_attendance"] = attendance_receipts + coverage["childcare_attendance"] = attendance_source_evidence if congressional_district_vintage_crosswalk_metadata is not None: coverage["congressional_district_vintage_crosswalk"] = ( congressional_district_vintage_crosswalk_metadata diff --git a/tools/prepare_us_childcare_attendance.py b/tools/prepare_us_childcare_attendance.py index d9e4507f3..2b0453ac5 100644 --- a/tools/prepare_us_childcare_attendance.py +++ b/tools/prepare_us_childcare_attendance.py @@ -20,6 +20,9 @@ write_frame_checkpoint, ) from microcosm.build.us_runtime import childcare_attendance, nsece_childcare +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + childcare_attendance_public_metadata, +) from microcosm.build.us_runtime.childcare_attendance_stage import ( export_native_childcare_candidate, inherit_outside_domain_attendance_baseline, @@ -46,6 +49,9 @@ from microcosm.build.us_runtime.nsece_childcare_dependence import ( fit_nsece_sibling_dependence, ) +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + assess_sibling_schedules, +) from microcosm.frame import Frame @@ -155,6 +161,7 @@ def main() -> None: if args.extended_assessment: report["selection_and_cross_validation"] = assess_nsece_childcare(source) report["masked_calendar_validation"] = assess_noncalendar_bridge(source) + report["sibling_schedule_validation"] = assess_sibling_schedules(source) if args.bridge_noncalendar: source = bridge_nsece_noncalendar_attendance(source, seed=args.seed) report["noncalendar_bridge"] = source.source_receipt["noncalendar_bridge"] @@ -259,8 +266,8 @@ def main() -> None: report["candidate_checkpoint_sha256"] = _sha256(args.output_checkpoint) report["parent_population_sha256"] = _sha256(input_path) report["production_stage_executed"] = args.production_stage - report["candidate_receipts"] = json.loads( - json.dumps(candidate.metadata, default=dict) + report["candidate_receipts"] = childcare_attendance_public_metadata( + candidate ) if args.output_native_h5: export_native_childcare_candidate( diff --git a/tools/validate_us_childcare_population.py b/tools/validate_us_childcare_population.py index e1f828b42..731dc89b3 100644 --- a/tools/validate_us_childcare_population.py +++ b/tools/validate_us_childcare_population.py @@ -10,7 +10,6 @@ from __future__ import annotations import argparse -import gc import hashlib import json from importlib.metadata import version @@ -23,8 +22,12 @@ from microcosm.build.us_runtime.childcare_attendance import ( US_CHILDCARE_ATTENDANCE_COLUMNS, ) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + childcare_attendance_public_metadata, +) +from microcosm.build.us_runtime.childcare_sensitivity import compare_childcare_scenarios from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 -from microcosm.calibrate.geography_constants import US_STATE_NUMERIC_FIPS_TO_POSTAL +from microcosm.frame import Frame def _sha256(path: Path) -> str: @@ -48,7 +51,13 @@ def main() -> None: parser.error("Parent population hash mismatch") parent = load_legacy_calibrated_us_h5(args.parent_h5) stored = load_frame_checkpoint(args.candidate_checkpoint) - candidate = stored.frame + frame = stored.frame + candidate = Frame( + {e: frame.table(e) for e in frame.entities}, + frame.schema, + {e: frame.weights_for(e) for e in frame.weighted_entities}, + metadata=stored.metadata.get("frame_metadata", {}), + ) for entity in parent.entities: pd.testing.assert_frame_equal( parent.table(entity), candidate.table(entity)[parent.table(entity).columns] @@ -101,7 +110,7 @@ def main() -> None: days[young] * attendance[young, 2], weights=person_weights[young] ) ), - "candidate_receipt": stored.metadata.get("frame_metadata", {}), + "candidate_receipt": childcare_attendance_public_metadata(candidate), "states": [], "production_ready": False, "interpretation": "attendance-only counterfactual; provider, activity, expenses and take-up inputs remain as in parent", @@ -129,52 +138,11 @@ def main() -> None: ), } ) - from policyengine_us import Microsimulation - from policyengine_us.data import USSingleYearDataset - try: - for state_fips, households in hh.groupby("state_fips", sort=True): - state = US_STATE_NUMERIC_FIPS_TO_POSTAL[int(state_fips)] - rows = p.person_household_id.isin(households.household_id) - people = parent.table("person").loc[rows].copy() - tables = {"person": people, "household": households} - for entity in parent.schema.group_entities: - if entity != "household": - table = parent.table(entity) - tables[entity] = table.loc[ - table[f"{entity}_id"].isin(people[f"person_{entity}_id"]) - ] - result = {"state": state, "sample_households": len(households)} - for name in ("baseline", "candidate"): - sim = Microsimulation( - dataset=USSingleYearDataset(**tables, time_period=args.year) - ) - if name == "candidate": - for column in US_CHILDCARE_ATTENDANCE_COLUMNS: - values = np.asarray(sim.calculate(column, args.year)).copy() - known = resolved[rows] - values[known] = p.loc[rows, column].to_numpy()[known] - sim.set_input(column, args.year, values) - variable = f"{state.lower()}_child_care_subsidies" - if variable not in sim.tax_benefit_system.variables: - raise ValueError(f"The pinned engine has no {variable}") - definition = sim.tax_benefit_system.variables[variable] - entity = definition.entity.key - amount = np.asarray(sim.calculate(variable, args.year), dtype=float) - weights = np.asarray( - sim.calculate(f"{entity}_weight", args.year), dtype=float - ) - if not np.isfinite(amount).all(): - raise ValueError(f"Nonfinite state benefit: {state}/{name}") - result[name] = { - "variable": variable, - "entity": entity, - "positive_sample_units": int((amount > 0).sum()), - "weighted_positive_units": float(weights[amount > 0].sum()), - "annual_modeled_benefits": float(amount @ weights), - } - del sim - gc.collect() + for result in compare_childcare_scenarios( + parent, {"candidate": attendance}, year=args.year + ): + state = result["state"] report["states"].append(result) print( f"{state}: {result['baseline']['positive_sample_units']} -> {result['candidate']['positive_sample_units']} positive units", diff --git a/tools/validate_us_childcare_sensitivity.py b/tools/validate_us_childcare_sensitivity.py new file mode 100644 index 000000000..753bcd608 --- /dev/null +++ b/tools/validate_us_childcare_sensitivity.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +"""Stress-test unidentified noncalendar days/irregular hours on a fixed parent. + +Outputs aggregate evidence only. This does not identify the missing schedule, +certify a dataset, or authorize publication. All output paths must be new. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from importlib.metadata import version +from pathlib import Path + +import numpy as np + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + attendance_recipe_identity, +) +from microcosm.build.us_runtime.childcare_population import ( + harmonize_asec_childcare_predictors, +) +from microcosm.build.us_runtime.childcare_sensitivity import ( + compare_childcare_scenarios, + noncalendar_sensitivity_source, +) +from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 +from microcosm.build.us_runtime.nsece_childcare import ( + NSECE_CHILDCARE_FALLBACK_COLUMNS, + NSECE_CHILDCARE_MATCH_COLUMNS, + load_nsece_childcare, + with_us_nsece_childcare_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_dependence import ( + fit_nsece_sibling_dependence, +) + + +def _sha256(path): + with Path(path).open("rb") as stream: + return hashlib.file_digest(stream, "sha256").hexdigest() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--parent-h5", type=Path, required=True) + parser.add_argument("--parent-sha256", required=True) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--asec-source-cache", type=Path, required=True) + parser.add_argument("--seed", type=int, default=915) + parser.add_argument("--year", type=int, default=2026) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("The report path must be new") + if _sha256(args.parent_h5) != args.parent_sha256: + parser.error("Parent population hash mismatch") + parent = load_legacy_calibrated_us_h5(args.parent_h5) + normalized = harmonize_asec_childcare_predictors( + parent, source_cache=args.asec_source_cache + ) + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + dependence = fit_nsece_sibling_dependence(source.children) + bridged = bridge_nsece_noncalendar_attendance(source, seed=args.seed) + arms = { + "candidate": bridged, + "no_modeled_irregular_hours": noncalendar_sensitivity_source( + bridged, irregular_hours=False + ), + "one_fewer_day": noncalendar_sensitivity_source(bridged, day_shift=-1), + "one_more_day": noncalendar_sensitivity_source(bridged, day_shift=1), + } + scenarios = {} + for name, donors in arms.items(): + transferred = with_us_nsece_childcare_attendance( + normalized, + donors, + seed=args.seed, + match_columns=NSECE_CHILDCARE_MATCH_COLUMNS, + fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS, + sibling_dependence=dependence["rho"], + ) + scenarios[name] = transferred.table("person")[ + list(US_CHILDCARE_ATTENDANCE_COLUMNS) + ].to_numpy(dtype=float) + print(f"Prepared {name}", flush=True) + people = normalized.table("person") + young = people.age.between(0, 12).to_numpy() + report = { + "parent_sha256": args.parent_sha256, + "source": source.source_receipt, + "recipe": attendance_recipe_identity(), + "seed": args.seed, + "engine_version": version("policyengine-us"), + "policy_year": args.year, + "code_sha256": { + str(p.name): _sha256(p) + for p in ( + Path(__file__), + Path(__file__).parents[1] + / "packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py", + ) + }, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "assumptions": { + name: donors.source_receipt.get("transport_sensitivity", {}) + for name, donors in arms.items() + }, + "changed_children": { + name: int( + np.any(values[young] != scenarios["candidate"][young], axis=1).sum() + ) + for name, values in scenarios.items() + }, + "screens": {"national_relative_change": 0.10, "state_relative_change": 0.20}, + "states": [], + "production_ready": False, + "interpretation": "assumption stress tests, not confidence intervals; potential modeled benefits, not calibrated spending", + } + try: + for result in compare_childcare_scenarios(parent, scenarios, year=args.year): + report["states"].append(result) + print(result["state"], flush=True) + totals = { + name: sum(row[name]["annual_modeled_benefits"] for row in report["states"]) + for name in ("baseline", *arms) + } + flags = [] + comparisons = [] + for name in arms: + if name == "candidate": + continue + for label, original, changed, limit in ( + ("US", totals["candidate"], totals[name], 0.10), + *( + ( + r["state"], + r["candidate"]["annual_modeled_benefits"], + r[name]["annual_modeled_benefits"], + 0.20, + ) + for r in report["states"] + ), + ): + relative = (changed - original) / original if original else None + flagged = ( + abs(relative) > limit if relative is not None else changed != 0 + ) + comparison = { + "scenario": name, + "state": label, + "relative_change": relative, + "absolute_change": changed - original, + "flagged": flagged, + } + comparisons.append(comparison) + if flagged: + flags.append(comparison) + report["summary"] = { + "states_evaluated": len(report["states"]), + "annual_potential_modeled_benefits": totals, + "sensitivity_flags": flags, + "comparisons": comparisons, + } + except Exception as exc: + report["error"] = f"{type(exc).__name__}: {exc}" + raise + finally: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text( + json.dumps(report, indent=2, allow_nan=False, default=dict) + "\n" + ) + + +if __name__ == "__main__": + main() From 5ef806554b3c7d61f135bf095f4ed083bea32f11 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Wed, 16 Sep 2026 15:55:20 -0400 Subject: [PATCH 07/16] Evaluate household-size attendance matching on reserved households --- docs/us-childcare-attendance.md | 11 + experiments/us-childcare-attendance/README.md | 100 ++- ...ousehold-review-artifact-verification.json | 44 ++ .../household-size-development.json | 734 ++++++++++++++++++ .../household-size-plan.txt | 34 + .../household-size-reserved-validation.json | 686 ++++++++++++++++ .../us_runtime/nsece_childcare_dependence.py | 12 +- .../nsece_childcare_sibling_validation.py | 216 +++++- .../tests/test_us_nsece_childcare.py | 162 ++++ tools/validate_us_childcare_household_size.py | 60 ++ 10 files changed, 2035 insertions(+), 24 deletions(-) create mode 100644 experiments/us-childcare-attendance/household-review-artifact-verification.json create mode 100644 experiments/us-childcare-attendance/household-size-development.json create mode 100644 experiments/us-childcare-attendance/household-size-plan.txt create mode 100644 experiments/us-childcare-attendance/household-size-reserved-validation.json create mode 100644 tools/validate_us_childcare_household_size.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index b4cbc17da..9edd77149 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -81,6 +81,17 @@ moments, correlations, and every child's contribution to totals in households with three or more children. Its provisional screens currently fail; matching the average participation rate does not qualify this household model. +The September 16 [household-size experiment](../experiments/us-childcare-attendance/README.md) +tested one additional predictor: all rostered under-13 children, capped at three. +The challenger improves larger-family mean days/hours on a separately reserved +internal partition, but still fails 8 of 15 joint-schedule screens and has much +sparser donor support. It remains diagnostic-only; the build still uses the +four matching fields above. Complete-household evaluation also selects children +with lower observed attendance than children whose siblings have unresolved +calendars. That selection is a separate limitation, not evidence that all +population attendance should simply be scaled downward. The survey had already +informed development, so the reserved comparison is not external validation. + ## ASEC target harmonization `harmonize_asec_childcare_predictors` resolves `PEPAR1` and `PEPAR2` against diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index b831ed251..307131120 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,4 +1,96 @@ -# NSECE attendance review revision — 2026-09-15 +# NSECE attendance household-model investigation — 2026-09-16 + +**PR #916 remains draft.** Adding household size improves larger-family means, +but the proposed matcher still fails joint-schedule validation. It is retained +as an experiment and **has not replaced the production-stage matching recipe**. + +- [Comparison plan recorded before the new results](household-size-plan.txt) +- [Development comparison and calendar-selection diagnostics](household-size-development.json) +- [Reserved-household comparison](household-size-reserved-validation.json) +- [Current artifact verification](household-review-artifact-verification.json) + +The challenger adds the number of rostered children ages 0–12, capped at three, +to the existing age/region/work/income matching. Missing calendars still count +toward household size. Its fallback retains size until the final age-only level. +Both donor selection and the sibling-dependence fit use the revised predictors. +The shared-rank mixture cannot change each child's conditional mean: adjusting +its dependence coefficient alone cannot correct a mean attendance discrepancy. + +Before fitting the challenger, a deterministic 20% household partition was +reserved. Every development donor pool and dependence fit excludes it. Five-fold +development used the remaining households; the fixed challenger was then scored +once on the reserved households. All metrics integrate weighted donor CDFs +exactly. Earlier diagnostics had already used this survey, so this is an +internal comparison, **not untouched external validation**. No model was retuned +after viewing the reserved results. + +| Reserved comparison, 128 complete households with 3+ children | Observed | Existing matcher | Size-conditioned challenger | +| --- | ---: | ---: | ---: | +| Mean total days/week | 4.332 | 5.892 (+36.0%) | 4.964 (+14.6%) | +| Mean total hours/week | 32.575 | 40.793 (+25.2%) | 33.430 (+2.6%) | +| SD of total hours/week | 54.828 | 49.677 | 43.246 (−21.1%) | + +The reserved comparison includes 376 complete sibling households overall. +The existing matcher fails 10 of 15 provisional screens; the challenger fails +8 of 15. Better larger-family means do not establish a realistic joint +distribution. For example, the youngest-pair weekly-hours cross-product error +in 3+ households increases from 18.8% to 36.6%. The challenger's dependence +coefficient reaches its upper bound of one in every development fit and the +reserved fit, yet several joint-participation/intensity checks still fail. +On the development folds, the larger-family hours mean is still 21.2% high. + +Two limitations prevent treating the mean improvement as a resolved model: + +1. **Sparse conditioning:** in the development pool, the median exact cell + falls from 12 donor households to 5. The share of observed children in cells + with fewer than ten donor households rises from 37.2% to 87.9%. These counts + precede fold exclusions, which can further reduce support; ten is a + descriptive cutoff, not a tuned matching rule. +2. **Selected validation households:** totals can be scored only when every + under-13 calendar is complete. In development households with 3+ children, + observed children in fully complete households average 9.86 hours/week; + observed children with unresolved siblings average 26.22. All observed + children in that size group average 12.62. These child-weighted means show + selection differences, not the missing children's outcomes or a causal + missingness effect. Forcing predictions down to the complete-household + average would not establish unbiased population attendance. + +**Decision:** do not adopt hard household-size strata on these results. The +next model design needs to control sparse-cell instability and assess calendar +selection explicitly, scoring all observed child marginals by household size +alongside complete-household joint moments. A partial-pooling or household-level +model requires a new evaluation plan; another split of this same inspected +survey would still not be external acceptance evidence. Noncalendar transport +sensitivity and older-child/provider gaps remain unresolved. + +Reproduce either partition, using a new report path for each run: + +```bash +uv run python tools/validate_us_childcare_household_size.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --partition development --report /local/household-development.json +# Repeat with --partition validation and a separate report path only after +# freezing the challenger; do not use those outcomes for further tuning. +``` + +The tool verifies the original source hashes and reports the plan, diagnostic +code, and recipe hashes. It writes aggregates only. Regression tests cover +reserved-household exclusion from every fit and donor pool, poisoning reserved +outcomes without changing development results, actual donor conditioning, +counting unresolved siblings, and rejecting reconstructed calendars as truth. + +The 593-test source/attendance/architecture regression run passed, as did lint, +formatting, the tracked CI test inventory, and exact source-byte checks for both +updated modules in the built wheel. The current recipe was rebuilt from the +original parent because the fitter's code hash changed. Both native loaders +validate its receipt; all 166,321 people's attendance values and IDs and all +57,240 household weights equal the previous candidate exactly. The default +dependence fit and sensitivity evaluator are unchanged, so the historical +benefit and sensitivity estimates still apply. These checks do not certify +the statistical model or authorize population publication. + +## Review revision — 2026-09-15 **PR #916 remains draft.** The engineering safeguards have been strengthened, but the expanded household diagnostics fail provisional statistical screens. @@ -50,9 +142,9 @@ weighted donor CDFs exactly; no favorable simulation seed is selected. Nine of fifteen provisional screens fail. Larger-household mean total days are 25.35% too high and hours 23.36% too high. A fitted binary-participation mixture -is not enough to establish realistic household schedules. A next model revision -should investigate household-size conditioning and joint schedule donors, then -be evaluated with separately reserved household evidence; retuning on these +is not enough to establish realistic household schedules. Household-size +conditioning was investigated using the separately reserved internal comparison above. +The tested hard household-size conditioning was not adopted; retuning on these folds would not create independent validation. ## Noncalendar assumption sensitivity diff --git a/experiments/us-childcare-attendance/household-review-artifact-verification.json b/experiments/us-childcare-attendance/household-review-artifact-verification.json new file mode 100644 index 000000000..5e34d4ac7 --- /dev/null +++ b/experiments/us-childcare-attendance/household-review-artifact-verification.json @@ -0,0 +1,44 @@ +{ + "date": "2026-09-16", + "people": 166321, + "under13_children": 31889, + "households": 57240, + "native_sha256": "fc84fa083061bb28732b03ae6b06168505fe28be58f72954f52bf99b95eed1e8", + "checkpoint_sha256": "d69901b04c049142bc4ceabcd4ac547c324b43c18799a86cd39a9eac2e66f256", + "previous_native_sha256": "937c0c0c9798f0cf9e015fc4d5bed204342480bf93a415d94d3080d66f20cb6f", + "source_stage_report_sha256": "cb26f9ddc19d2924ac303c15b66a94f5d393c435ab55922309d2f24fa16fc3d2", + "attendance_recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "content_binding_sha256": "80282b5b68b744768d49a455adc2ffc5b931f70507069eea4b45223c7fce2f46", + "verified_native_loaders": [ + "microcosm.build.us_runtime.h5_io.load_legacy_calibrated_us_h5", + "microcosm.build.us_runtime.l0_refit_export.load_us_frame" + ], + "all_attendance_values_and_person_ids_equal_previous_candidate": true, + "household_weights_equal_previous_candidate": true, + "original_columns_and_period_verified_by_source_stage_export": true, + "build_wheel_sha256": "7c10e316c709be5a5ed0db5a82faf2646f189ee337c7ed0d23d60b1a1e777c24", + "verification_code_sha256": "6dda5a07a99d4693719d344cb33729d3431d08909f6ee9930c665fc0ac236f12", + "interpretation": "The household-size challenger is diagnostic-only. The default dependence fitter accepts explicit matching columns for assessment but retains its original production defaults. Rebuilt from the unchanged original parent because recipe code hashes changed; every attendance value equals the previous candidate, so its benefit comparison and transport-sensitivity estimates remain applicable. Neither the old nor current candidate is certified.", + "default_sibling_fit_matches_previous_execution": true, + "changed_recipe_files": [ + "nsece_childcare_dependence.py" + ], + "sensitivity_model_and_evaluator_code_unchanged": true, + "production_ready": false +} diff --git a/experiments/us-childcare-attendance/household-size-development.json b/experiments/us-childcare-attendance/household-size-development.json new file mode 100644 index 000000000..e0a5c2eae --- /dev/null +++ b/experiments/us-childcare-attendance/household-size-development.json @@ -0,0 +1,734 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "partition": "development", + "validation_seed": 20260916, + "legacy": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": 20260916, + "partition": "development", + "splits": [ + { + "training_households": 4097, + "evaluation_households": 1039, + "household_overlap": 0 + }, + { + "training_households": 4112, + "evaluation_households": 1024, + "household_overlap": 0 + }, + { + "training_households": 4073, + "evaluation_households": 1063, + "household_overlap": 0 + }, + { + "training_households": 4113, + "evaluation_households": 1023, + "household_overlap": 0 + }, + { + "training_households": 4149, + "evaluation_households": 987, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,region,parent_work_status,income_band": 3745, + "age,parent_work_status,income_band": 97, + "age,parent_work_status": 15 + }, + "training_rho": [ + 0.6848203293890904, + 0.8388331395514941, + 0.7672584653221455, + 0.913950703165035, + 0.801448169860078 + ], + "youngest_pairs": { + "households": 1565, + "household_weight": 6748306.0591101395, + "observed": { + "participation": { + "joint_product": 0.3199254439129754, + "correlation": 0.5813359742882304 + }, + "days": { + "joint_product": 5.606681648963777, + "correlation": 0.5820637734098711 + }, + "weekly_hours": { + "joint_product": 338.57812172627393, + "correlation": 0.548207236935218 + } + }, + "independent": { + "participation": { + "joint_product": 0.2575450863451372, + "correlation": 0.13528005655281253 + }, + "days": { + "joint_product": 4.48267591905156, + "correlation": 0.14390476225719756 + }, + "weekly_hours": { + "joint_product": 257.2445077783002, + "correlation": 0.1160908773407519 + } + }, + "coupled": { + "participation": { + "joint_product": 0.31482088549744375, + "correlation": 0.36593111538647133 + }, + "days": { + "joint_product": 5.938130785995765, + "correlation": 0.41016522348600426 + }, + "weekly_hours": { + "joint_product": 359.51959348847805, + "correlation": 0.32031500176565186 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 533, + "household_weight": 2007819.6139549948, + "observed": { + "participation": { + "joint_product": 0.2807799138598989, + "correlation": 0.5474984886830093 + }, + "days": { + "joint_product": 5.043798878017195, + "correlation": 0.5641177009261245 + }, + "weekly_hours": { + "joint_product": 302.8480101771769, + "correlation": 0.5286710123883716 + } + }, + "independent": { + "participation": { + "joint_product": 0.2722007521680011, + "correlation": 0.18155884264108704 + }, + "days": { + "joint_product": 4.833198372869685, + "correlation": 0.16973032613323838 + }, + "weekly_hours": { + "joint_product": 273.58655003554566, + "correlation": 0.1281191585638751 + } + }, + "coupled": { + "participation": { + "joint_product": 0.32553953994339074, + "correlation": 0.3960658173279713 + }, + "days": { + "joint_product": 6.159314398463845, + "correlation": 0.40625610412960306 + }, + "weekly_hours": { + "joint_product": 374.7187606337127, + "correlation": 0.31981724026910857 + } + } + }, + "larger_households": { + "households": 533, + "observed": { + "mean_total_days": 4.549551457601687, + "mean_total_weekly_hours": 33.199873558867296, + "sd_total_days": 5.77030590861846, + "sd_total_weekly_hours": 56.523051645186115, + "all_children_attend": 0.19377899515538355 + }, + "independent": { + "mean_total_days": 5.64924422646984, + "mean_total_weekly_hours": 40.48300647511985, + "sd_total_days": 4.608639098038089, + "sd_total_weekly_hours": 43.22268543690276, + "all_children_attend": 0.10705608714104596 + }, + "coupled": { + "mean_total_days": 5.64924422646984, + "mean_total_weekly_hours": 40.48300647511985, + "sd_total_days": 5.565444503780002, + "sd_total_weekly_hours": 50.662241267526234, + "all_children_attend": 0.1842946017253324 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.005104558415531646, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.17189854992386688, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.22789223516956614, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.059116810581397256, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.06185122551756138, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.04475962608349182, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.1578615967965214, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.20885377211926298, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.22116574181982035, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.23731623798514923, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.24171454683312, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.21937230885348705, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.03550269397892398, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.10368885272596613, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.009484393430051141, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "household_size": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": 20260916, + "partition": "development", + "splits": [ + { + "training_households": 4097, + "evaluation_households": 1039, + "household_overlap": 0 + }, + { + "training_households": 4112, + "evaluation_households": 1024, + "household_overlap": 0 + }, + { + "training_households": 4073, + "evaluation_households": 1063, + "household_overlap": 0 + }, + { + "training_households": 4113, + "evaluation_households": 1023, + "household_overlap": 0 + }, + { + "training_households": 4149, + "evaluation_households": 987, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "childcare_household_size", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band" + ], + [ + "age", + "childcare_household_size", + "parent_work_status" + ], + [ + "age", + "childcare_household_size" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,childcare_household_size,region,parent_work_status,income_band": 3428, + "age,childcare_household_size,parent_work_status,income_band": 342, + "age,childcare_household_size,parent_work_status": 79, + "age,childcare_household_size": 8 + }, + "training_rho": [ + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "youngest_pairs": { + "households": 1565, + "household_weight": 6748306.0591101395, + "observed": { + "participation": { + "joint_product": 0.3199254439129754, + "correlation": 0.5813359742882304 + }, + "days": { + "joint_product": 5.606681648963777, + "correlation": 0.5820637734098711 + }, + "weekly_hours": { + "joint_product": 338.57812172627393, + "correlation": 0.548207236935218 + } + }, + "independent": { + "participation": { + "joint_product": 0.2424157631812536, + "correlation": 0.15684839780606782 + }, + "days": { + "joint_product": 4.188249209802441, + "correlation": 0.17515292850190187 + }, + "weekly_hours": { + "joint_product": 235.4304608642769, + "correlation": 0.12984658658248008 + } + }, + "coupled": { + "participation": { + "joint_product": 0.2802071612829887, + "correlation": 0.3103237669397316 + }, + "days": { + "joint_product": 5.040019514661833, + "correlation": 0.33551387728285753 + }, + "weekly_hours": { + "joint_product": 304.05864836293415, + "correlation": 0.27086267382784396 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 533, + "household_weight": 2007819.6139549948, + "observed": { + "participation": { + "joint_product": 0.2807799138598989, + "correlation": 0.5474984886830093 + }, + "days": { + "joint_product": 5.043798878017195, + "correlation": 0.5641177009261245 + }, + "weekly_hours": { + "joint_product": 302.8480101771769, + "correlation": 0.5286710123883716 + } + }, + "independent": { + "participation": { + "joint_product": 0.2306517201772058, + "correlation": 0.16098841763316635 + }, + "days": { + "joint_product": 4.50046704664406, + "correlation": 0.22181638064040868 + }, + "weekly_hours": { + "joint_product": 298.87830451258816, + "correlation": 0.15259248561190691 + } + }, + "coupled": { + "participation": { + "joint_product": 0.2649318815369116, + "correlation": 0.30106180716897935 + }, + "days": { + "joint_product": 5.25974745290275, + "correlation": 0.3586483188719107 + }, + "weekly_hours": { + "joint_product": 372.6836980853711, + "correlation": 0.2685558001776592 + } + } + }, + "larger_households": { + "households": 533, + "observed": { + "mean_total_days": 4.549551457601687, + "mean_total_weekly_hours": 33.199873558867296, + "sd_total_days": 5.77030590861846, + "sd_total_weekly_hours": 56.523051645186115, + "all_children_attend": 0.19377899515538355 + }, + "independent": { + "mean_total_days": 5.257588507232319, + "mean_total_weekly_hours": 40.23091201749012, + "sd_total_days": 4.882049352378196, + "sd_total_weekly_hours": 49.29942433861291, + "all_children_attend": 0.08720607874073927 + }, + "coupled": { + "mean_total_days": 5.257588507232319, + "mean_total_weekly_hours": 40.23091201749012, + "sd_total_days": 5.405854455301145, + "sd_total_weekly_hours": 53.74796429671431, + "all_children_attend": 0.12330025112746766 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.03971828262998667, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.2465498961270136, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.27734456310737404, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.10106907610969385, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.1019542349261637, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.015848032322987315, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.20546938205421378, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.2601152122107124, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.04281466809208862, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.23059648920043363, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.15562788029303362, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.21177907337978147, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.06315981493684325, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.04909655915062668, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.07047874402791589, + "relative": false, + "limit": 0.05, + "passed": false + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "development_selection": { + "partition": "development only", + "observed_child_comparisons": [ + { + "household_size_capped_at_3": 1, + "sample": "all_observed_children", + "children": 1534, + "households": 1534, + "child_weight": 7081698.15383859, + "mean_days": 2.12170224791113, + "mean_weekly_hours": 16.593123351196084 + }, + { + "household_size_capped_at_3": 1, + "sample": "children_in_complete_households", + "children": 1534, + "households": 1534, + "child_weight": 7081698.15383859, + "mean_days": 2.12170224791113, + "mean_weekly_hours": 16.593123351196084 + }, + { + "household_size_capped_at_3": 2, + "sample": "all_observed_children", + "children": 2291, + "households": 1259, + "child_weight": 10714272.764753226, + "mean_days": 1.8174291558486764, + "mean_weekly_hours": 12.95845201473953 + }, + { + "household_size_capped_at_3": 2, + "sample": "children_in_complete_households", + "children": 2064, + "households": 1032, + "child_weight": 9548734.356105652, + "mean_days": 1.71581007929574, + "mean_weekly_hours": 11.804463953672407 + }, + { + "household_size_capped_at_3": 2, + "sample": "observed_children_with_unresolved_siblings", + "children": 227, + "households": 227, + "child_weight": 1165538.408647576, + "mean_days": 2.6499487539309645, + "mean_weekly_hours": 22.412559544351797 + }, + { + "household_size_capped_at_3": 3, + "sample": "all_observed_children", + "children": 2177, + "households": 723, + "child_weight": 8060168.091557396, + "mean_days": 1.6268475611504107, + "mean_weekly_hours": 12.615632989129697 + }, + { + "household_size_capped_at_3": 3, + "sample": "children_in_complete_households", + "children": 1793, + "households": 533, + "child_weight": 6701383.24976393, + "mean_days": 1.36015412333186, + "mean_weekly_hours": 9.85801688518962 + }, + { + "household_size_capped_at_3": 3, + "sample": "observed_children_with_unresolved_siblings", + "children": 384, + "households": 190, + "child_weight": 1358784.8417934654, + "mean_days": 2.942151413569196, + "mean_weekly_hours": 26.215904201922807 + } + ], + "donor_support": { + "legacy": { + "children": 6002, + "median_donor_households": 12.0, + "children_in_cells_below_10_households": 2233, + "fraction_of_children_in_cells_below_10_households": 0.3720426524491836, + "definition": "Exact matching cells in the whole development pool; fold-training support can be smaller. Ten is a descriptive cutoff, not a tuned fallback rule." + }, + "household_size": { + "children": 6002, + "median_donor_households": 5.0, + "children_in_cells_below_10_households": 5275, + "fraction_of_children_in_cells_below_10_households": 0.8788737087637454, + "definition": "Exact matching cells in the whole development pool; fold-training support can be smaller. Ten is a descriptive cutoff, not a tuned fallback rule." + } + }, + "interpretation": "Descriptive differences under calendar selection; not a causal effect of missingness and not corrected population totals." + }, + "production_recipe_changed": false, + "production_ready": false, + "interpretation": "Prospective internal comparison on previously inspected survey data; not untouched external acceptance evidence. Complete-household totals do not identify totals for families with missing calendars.", + "plan_sha256": "7a75b47e280833a83d97a51a7fd29e210d1144e93aa5d7f2571d3146e619bedc", + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "diagnostic_code_sha256": { + "validate_us_childcare_household_size.py": "066f081a01329f0bbfa7d19b39c62a3ca9c95969a9c200f7f4d544d1390c0f65", + "nsece_childcare_sibling_validation.py": "8f3a6a2ce3813e251ded0b5273f3e3e5ac7504f6b724b881f289263a2f13b86f" + } +} diff --git a/experiments/us-childcare-attendance/household-size-plan.txt b/experiments/us-childcare-attendance/household-size-plan.txt new file mode 100644 index 000000000..c687bf908 --- /dev/null +++ b/experiments/us-childcare-attendance/household-size-plan.txt @@ -0,0 +1,34 @@ +Household-size comparison specified before new results, 2026-09-16. + +Hypothesis: the shared-rank mixture cannot change conditional marginal means. +The 3+ household mean bias is therefore a donor-conditioning problem, not a rho +tuning problem. Compare the current age/region/work/income matcher with exactly +one revision: add the count of all resident children ages 0-12, capped at 3 +(1, 2, 3+). Count children before selecting complete calendars so missing +attendance never changes the predictor. Derive the same count from physical +target household membership, not from repeated source-person clone IDs. + +Candidate fallback: age/count/region/work/income, age/count/work/income, +age/count/work, age/count, age. Report every fallback, including loss of count. +Use the same child/household survey weights and binary sibling-mixture fit; +no tuning of rho to the intensity screens and no favorable simulation seed. + +Reserve households with SHA256('20260916:reserved:') first eight +bytes interpreted big endian modulo 5 equal to zero. Exclude these households +from all development donor pools and dependence fits. Five-fold development +uses existing seed 271828 within the remaining households. Freeze the candidate +before scoring the reserved partition once; train on all remaining households. +Compare both arms on the identical partitions and existing 15 screens. Do not +iterate on reserved outcomes. Require improvement in the larger-family mean +bias without presenting a failed screen as a qualified model. + +The source has already informed earlier development, including aggregate +results covering these households. This is a prospective internal comparison, +not untouched external evidence. Independent source validation and release +certification remain outstanding even if all provisional screens pass. + +If adopted as a draft candidate, derive the predictor in both source/target, +use it consistently in bridge, dependence fit, transfer and diagnostics, then +rebuild from the original parent. Re-run population outcomes and noncalendar +sensitivity on the new candidate; old benefit results must be labeled historical. +Publish only aggregate diagnostics, never raw source records or household IDs. diff --git a/experiments/us-childcare-attendance/household-size-reserved-validation.json b/experiments/us-childcare-attendance/household-size-reserved-validation.json new file mode 100644 index 000000000..9b7a906ad --- /dev/null +++ b/experiments/us-childcare-attendance/household-size-reserved-validation.json @@ -0,0 +1,686 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "partition": "validation", + "validation_seed": 20260916, + "legacy": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": 20260916, + "partition": "validation", + "splits": [ + { + "training_households": 5136, + "evaluation_households": 1233, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,region,parent_work_status,income_band": 912, + "age,parent_work_status,income_band": 15, + "age,parent_work_status": 5 + }, + "training_rho": [ + 0.7511342795267243 + ], + "youngest_pairs": { + "households": 376, + "household_weight": 1581250.458351494, + "observed": { + "participation": { + "joint_product": 0.36556088999422404, + "correlation": 0.5610984527348091 + }, + "days": { + "joint_product": 7.242197412151899, + "correlation": 0.5718708775247285 + }, + "weekly_hours": { + "joint_product": 491.8807617182249, + "correlation": 0.4702933377588588 + } + }, + "independent": { + "participation": { + "joint_product": 0.2648063070266509, + "correlation": 0.18506959808631887 + }, + "days": { + "joint_product": 4.679792048576763, + "correlation": 0.16873400086608684 + }, + "weekly_hours": { + "joint_product": 266.85090114375265, + "correlation": 0.11707605097373619 + } + }, + "coupled": { + "participation": { + "joint_product": 0.32269826366104903, + "correlation": 0.41833449873311257 + }, + "days": { + "joint_product": 6.230317317123342, + "correlation": 0.45223091173665864 + }, + "weekly_hours": { + "joint_product": 377.31068164279094, + "correlation": 0.3339999920804771 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 128, + "household_weight": 450849.53656700696, + "observed": { + "participation": { + "joint_product": 0.23778406757541562, + "correlation": 0.48592489298077784 + }, + "days": { + "joint_product": 5.267810680515874, + "correlation": 0.5703562333340767 + }, + "weekly_hours": { + "joint_product": 490.9855608342772, + "correlation": 0.6762830071141811 + } + }, + "independent": { + "participation": { + "joint_product": 0.2716208881703992, + "correlation": 0.19301700563340585 + }, + "days": { + "joint_product": 5.057022917507661, + "correlation": 0.19768961582359243 + }, + "weekly_hours": { + "joint_product": 295.49337418852684, + "correlation": 0.1628491947082673 + } + }, + "coupled": { + "participation": { + "joint_product": 0.327632580263415, + "correlation": 0.4185796047514795 + }, + "days": { + "joint_product": 6.552290048872771, + "correlation": 0.4659225374235249 + }, + "weekly_hours": { + "joint_product": 398.86474192922077, + "correlation": 0.36378630854465915 + } + } + }, + "larger_households": { + "households": 128, + "observed": { + "mean_total_days": 4.331937209253313, + "mean_total_weekly_hours": 32.575462647390545, + "sd_total_days": 5.739597650319324, + "sd_total_weekly_hours": 54.82768623012342, + "all_children_attend": 0.14964414915102348 + }, + "independent": { + "mean_total_days": 5.891851945228354, + "mean_total_weekly_hours": 40.79292481988039, + "sd_total_days": 4.731073923959437, + "sd_total_weekly_hours": 41.37835826307807, + "all_children_attend": 0.11201676861081947 + }, + "coupled": { + "mean_total_days": 5.891851945228354, + "mean_total_weekly_hours": 40.79292481988039, + "sd_total_days": 5.925772998641244, + "sd_total_weekly_hours": 49.67736343959238, + "all_children_attend": 0.20405555258916724 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.042862626333175, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.1196399657880699, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.1362933456783817, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.1397200376408814, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.23292246615871046, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.0898485126879994, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.1044336959105518, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.31249669856952195, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.24383552224225127, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.1876242933672545, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.36009634041854455, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.25225926217652983, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.032437003369314966, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.09393653361394896, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.05441140343814377, + "relative": false, + "limit": 0.05, + "passed": false + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "household_size": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": 20260916, + "partition": "validation", + "splits": [ + { + "training_households": 5136, + "evaluation_households": 1233, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "childcare_household_size", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band" + ], + [ + "age", + "childcare_household_size", + "parent_work_status" + ], + [ + "age", + "childcare_household_size" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,childcare_household_size,region,parent_work_status,income_band": 870, + "age,childcare_household_size,parent_work_status,income_band": 46, + "age,childcare_household_size,parent_work_status": 15, + "age,childcare_household_size": 1 + }, + "training_rho": [ + 1.0 + ], + "youngest_pairs": { + "households": 376, + "household_weight": 1581250.458351494, + "observed": { + "participation": { + "joint_product": 0.36556088999422404, + "correlation": 0.5610984527348091 + }, + "days": { + "joint_product": 7.242197412151899, + "correlation": 0.5718708775247285 + }, + "weekly_hours": { + "joint_product": 491.8807617182249, + "correlation": 0.4702933377588588 + } + }, + "independent": { + "participation": { + "joint_product": 0.2619313016547457, + "correlation": 0.18282163006736646 + }, + "days": { + "joint_product": 4.777462329366674, + "correlation": 0.21743973520841575 + }, + "weekly_hours": { + "joint_product": 261.8980750325785, + "correlation": 0.17614513311728405 + } + }, + "coupled": { + "participation": { + "joint_product": 0.31073293503588245, + "correlation": 0.3804640910760853 + }, + "days": { + "joint_product": 6.014701732050489, + "correlation": 0.4510904179360654 + }, + "weekly_hours": { + "joint_product": 350.1657257334819, + "correlation": 0.3797013683288874 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 128, + "household_weight": 450849.53656700696, + "observed": { + "participation": { + "joint_product": 0.23778406757541562, + "correlation": 0.48592489298077784 + }, + "days": { + "joint_product": 5.267810680515874, + "correlation": 0.5703562333340767 + }, + "weekly_hours": { + "joint_product": 490.9855608342772, + "correlation": 0.6762830071141811 + } + }, + "independent": { + "participation": { + "joint_product": 0.2614819428939225, + "correlation": 0.1654506920712637 + }, + "days": { + "joint_product": 4.098115261200414, + "correlation": 0.17691033926301059 + }, + "weekly_hours": { + "joint_product": 247.38304587866466, + "correlation": 0.21903583902041535 + } + }, + "coupled": { + "participation": { + "joint_product": 0.2953577249767762, + "correlation": 0.30323468986899804 + }, + "days": { + "joint_product": 5.152178159534045, + "correlation": 0.38887710741586745 + }, + "weekly_hours": { + "joint_product": 311.10123271641834, + "correlation": 0.3737655456308671 + } + } + }, + "larger_households": { + "households": 128, + "observed": { + "mean_total_days": 4.331937209253313, + "mean_total_weekly_hours": 32.575462647390545, + "sd_total_days": 5.739597650319324, + "sd_total_weekly_hours": 54.82768623012342, + "all_children_attend": 0.14964414915102348 + }, + "independent": { + "mean_total_days": 4.964349099595973, + "mean_total_weekly_hours": 33.4300784983736, + "sd_total_days": 4.362529218182336, + "sd_total_weekly_hours": 37.29191742464737, + "all_children_attend": 0.09245331473888625 + }, + "coupled": { + "mean_total_days": 4.964349099595973, + "mean_total_weekly_hours": 33.4300784983736, + "sd_total_days": 5.254529336333213, + "sd_total_weekly_hours": 43.245663726524754, + "all_children_attend": 0.14176731288100003 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.054827954958341585, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.12078045958866312, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.09059196942997139, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.1694921596643801, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.28810851534365317, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.057573657401360584, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.18147912591820925, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.302517461483314, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.021950773859341723, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.3663739679272877, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.1459882403170075, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.02623495666765337, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.08451259888558985, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.2112440502228466, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.007876836270023446, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "development_selection": { + "partition": "development only", + "observed_child_comparisons": [ + { + "household_size_capped_at_3": 1, + "sample": "all_observed_children", + "children": 1534, + "households": 1534, + "child_weight": 7081698.15383859, + "mean_days": 2.12170224791113, + "mean_weekly_hours": 16.593123351196084 + }, + { + "household_size_capped_at_3": 1, + "sample": "children_in_complete_households", + "children": 1534, + "households": 1534, + "child_weight": 7081698.15383859, + "mean_days": 2.12170224791113, + "mean_weekly_hours": 16.593123351196084 + }, + { + "household_size_capped_at_3": 2, + "sample": "all_observed_children", + "children": 2291, + "households": 1259, + "child_weight": 10714272.764753226, + "mean_days": 1.8174291558486764, + "mean_weekly_hours": 12.95845201473953 + }, + { + "household_size_capped_at_3": 2, + "sample": "children_in_complete_households", + "children": 2064, + "households": 1032, + "child_weight": 9548734.356105652, + "mean_days": 1.71581007929574, + "mean_weekly_hours": 11.804463953672407 + }, + { + "household_size_capped_at_3": 2, + "sample": "observed_children_with_unresolved_siblings", + "children": 227, + "households": 227, + "child_weight": 1165538.408647576, + "mean_days": 2.6499487539309645, + "mean_weekly_hours": 22.412559544351797 + }, + { + "household_size_capped_at_3": 3, + "sample": "all_observed_children", + "children": 2177, + "households": 723, + "child_weight": 8060168.091557396, + "mean_days": 1.6268475611504107, + "mean_weekly_hours": 12.615632989129697 + }, + { + "household_size_capped_at_3": 3, + "sample": "children_in_complete_households", + "children": 1793, + "households": 533, + "child_weight": 6701383.24976393, + "mean_days": 1.36015412333186, + "mean_weekly_hours": 9.85801688518962 + }, + { + "household_size_capped_at_3": 3, + "sample": "observed_children_with_unresolved_siblings", + "children": 384, + "households": 190, + "child_weight": 1358784.8417934654, + "mean_days": 2.942151413569196, + "mean_weekly_hours": 26.215904201922807 + } + ], + "donor_support": { + "legacy": { + "children": 6002, + "median_donor_households": 12.0, + "children_in_cells_below_10_households": 2233, + "fraction_of_children_in_cells_below_10_households": 0.3720426524491836, + "definition": "Exact matching cells in the whole development pool; fold-training support can be smaller. Ten is a descriptive cutoff, not a tuned fallback rule." + }, + "household_size": { + "children": 6002, + "median_donor_households": 5.0, + "children_in_cells_below_10_households": 5275, + "fraction_of_children_in_cells_below_10_households": 0.8788737087637454, + "definition": "Exact matching cells in the whole development pool; fold-training support can be smaller. Ten is a descriptive cutoff, not a tuned fallback rule." + } + }, + "interpretation": "Descriptive differences under calendar selection; not a causal effect of missingness and not corrected population totals." + }, + "production_recipe_changed": false, + "production_ready": false, + "interpretation": "Prospective internal comparison on previously inspected survey data; not untouched external acceptance evidence. Complete-household totals do not identify totals for families with missing calendars.", + "plan_sha256": "7a75b47e280833a83d97a51a7fd29e210d1144e93aa5d7f2571d3146e619bedc", + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "diagnostic_code_sha256": { + "validate_us_childcare_household_size.py": "066f081a01329f0bbfa7d19b39c62a3ca9c95969a9c200f7f4d544d1390c0f65", + "nsece_childcare_sibling_validation.py": "8f3a6a2ce3813e251ded0b5273f3e3e5ac7504f6b724b881f289263a2f13b86f" + } +} diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py index 932e1afe0..bbd5d26ba 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_dependence.py @@ -29,21 +29,21 @@ def complete_sibling_pairs(children): ) -def fit_nsece_sibling_dependence(children) -> dict: +def fit_nsece_sibling_dependence( + children, *, match_columns=NSECE_CHILDCARE_MATCH_COLUMNS +) -> dict: pool = children.loc[children.attendance_status.eq("complete")].copy() pool["weighted_care"] = (pool.childcare_days_per_week > 0) * pool.child_weight - cells = pool.groupby(list(NSECE_CHILDCARE_MATCH_COLUMNS))[ - ["weighted_care", "child_weight"] - ].sum() + cells = pool.groupby(list(match_columns))[["weighted_care", "child_weight"]].sum() cells["probability"] = cells.weighted_care / cells.child_weight pairs = complete_sibling_pairs(children) if pairs.empty: return {"rho": 0.0, "households": 0, "status": "no measured sibling pairs"} predicted = ( - pairs[list(NSECE_CHILDCARE_MATCH_COLUMNS)] + pairs[list(match_columns)] .merge( cells[["probability"]], - left_on=list(NSECE_CHILDCARE_MATCH_COLUMNS), + left_on=list(match_columns), right_index=True, how="left", validate="many_to_one", diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py index e5afb5ee1..498179b6a 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py @@ -18,6 +18,7 @@ from microcosm.build.us_runtime.nsece_childcare import ( NSECE_CHILDCARE_FALLBACK_COLUMNS, NSECE_CHILDCARE_MATCH_COLUMNS, + NSECEChildcareSource, ) from microcosm.build.us_runtime.nsece_childcare_dependence import ( fit_nsece_sibling_dependence, @@ -64,24 +65,87 @@ def _pair_summary(records): } -def assess_sibling_schedules(source, *, seed=271828): - """Evaluate youngest-pair days/hours and all-child totals for 3+ households.""" - children = source.children.loc[source.children.age.between(0, 12)].copy() - match_columns = NSECE_CHILDCARE_MATCH_COLUMNS - levels = (match_columns, *NSECE_CHILDCARE_FALLBACK_COLUMNS) - folds = children.source_household_id.map( +def _household_splits(children, *, seed, validation_seed, partition): + """Keep the reserved households outside every development fit and donor pool.""" + if partition not in ("all", "development", "validation"): + raise ValueError("Unknown sibling assessment partition.") + if (partition == "all") != (validation_seed is None): + raise ValueError("A reserved assessment needs a validation seed and partition.") + households = children.source_household_id + reserved = ( + households.map( + lambda x: ( + int.from_bytes( + hashlib.sha256(f"{validation_seed}:reserved:{x}".encode()).digest()[ + :8 + ], + "big", + ) + % 5 + == 0 + ) + ) + if validation_seed is not None + else pd.Series(False, index=children.index) + ) + if partition == "validation": + return [(~reserved, reserved)] + development = ~reserved + folds = households.map( lambda x: ( int.from_bytes(hashlib.sha256(f"{seed}:{x}".encode()).digest()[:8], "big") % 5 ) ) + return [ + (development & (folds != fold), development & (folds == fold)) + for fold in range(5) + ] + + +def assess_sibling_schedules( + source, + *, + seed=271828, + match_columns=NSECE_CHILDCARE_MATCH_COLUMNS, + fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS, + validation_seed=None, + partition="all", +): + """Evaluate pair intensity and larger-household totals with disjoint training. + + A reserved split separates model development from the final comparison. + Previously inspected source data do not become untouched external evidence + merely because a new split is used. + """ + children = source.children.loc[source.children.age.between(0, 12)].copy() + if children.attendance_status.eq("summary_bridge").any(): + raise ValueError("Sibling validation requires original measured calendars.") + levels = (match_columns, *fallback_match_columns) + splits = _household_splits( + children, seed=seed, validation_seed=validation_seed, partition=partition + ) pairs, large_pairs, larger = [], [], [] month, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS del month - fold_fits = [] - for fold in range(5): - training = children.loc[folds != fold] - rho = fit_nsece_sibling_dependence(training)["rho"] + fold_fits, split_counts, matching_counts = [], [], {} + for training_mask, target_mask in splits: + training = children.loc[training_mask] + target = children.loc[target_mask] + if training.empty or target.empty: + raise ValueError( + "Sibling assessment requires nonempty household partitions." + ) + if not set(training.source_household_id).isdisjoint(target.source_household_id): + raise ValueError("Sibling assessment leaks households across partitions.") + split_counts.append( + { + "training_households": int(training.source_household_id.nunique()), + "evaluation_households": int(target.source_household_id.nunique()), + "household_overlap": 0, + } + ) + rho = fit_nsece_sibling_dependence(training, match_columns=match_columns)["rho"] fold_fits.append(rho) train = training.loc[training.attendance_status.eq("complete")].sort_values( [days, hours, "donor_id"] @@ -114,10 +178,12 @@ def distribution( ).astype(float) cumulative = probabilities.cumsum() cumulative[-1] = 1 - cache[key] = (values, cumulative, probabilities) - return cache[key] + cache[key] = (values, cumulative, probabilities, ",".join(level)) + *distribution_values, used_level = cache[key] + matching_counts[used_level] = matching_counts.get(used_level, 0) + 1 + return distribution_values - for _, household in children.loc[folds == fold].groupby("source_household_id"): + for _, household in target.groupby("source_household_id"): if ( len(household) < 2 or not household.attendance_status.eq("complete").all() @@ -199,9 +265,14 @@ def distribution( ) ) result = { - "design": "five household-separated folds; exact integration of the implemented shared-rank donor CDFs", + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", "seed": seed, + "validation_seed": validation_seed, + "partition": partition, + "splits": split_counts, "match_columns": match_columns, + "fallback_match_columns": fallback_match_columns, + "matching_counts": matching_counts, "training_rho": fold_fits, "youngest_pairs": _pair_summary(pairs) if pairs else None, "youngest_pairs_in_3plus_households": _pair_summary(large_pairs) @@ -283,3 +354,120 @@ def check(name, observed, predicted, limit, relative=False): "checks": checks, "interpretation": "provisional diagnostic screens; not publication authorization", } + + +def compare_household_size_matching(source, *, partition, validation_seed=20260916): + """Evaluate one size-conditioned challenger without changing the build recipe. + + Count every rostered under-13 child before attendance selection. The + selection diagnostic uses only development households even when the + caller requests the separately reserved final comparison. + """ + if partition not in ("development", "validation"): + raise ValueError("Household-size comparison requires a reserved partition.") + children = source.children.copy() + children["childcare_household_size"] = ( + children.age.between(0, 12) + .groupby(children.source_household_id) + .transform("sum") + .clip(upper=3) + ) + revised_source = NSECEChildcareSource( + children, source.weights, source.source_receipt + ) + columns = ("age", "childcare_household_size", *NSECE_CHILDCARE_MATCH_COLUMNS[1:]) + fallbacks = tuple( + ("age", "childcare_household_size", *level[1:]) + for level in NSECE_CHILDCARE_FALLBACK_COLUMNS + ) + (("age",),) + settings = {"partition": partition, "validation_seed": validation_seed} + baseline = assess_sibling_schedules(source, **settings) + challenger = assess_sibling_schedules( + revised_source, + match_columns=columns, + fallback_match_columns=fallbacks, + **settings, + ) + return { + "source": source.source_receipt, + "partition": partition, + "validation_seed": validation_seed, + "legacy": baseline, + "household_size": challenger, + "development_selection": _household_selection_diagnostic( + children, match_columns=columns, validation_seed=validation_seed + ), + "production_recipe_changed": False, + "production_ready": False, + "interpretation": ( + "Prospective internal comparison on previously inspected survey data; " + "not untouched external acceptance evidence. Complete-household totals " + "do not identify totals for families with missing calendars." + ), + } + + +def _household_selection_diagnostic(children, *, match_columns, validation_seed): + children = children.loc[children.age.between(0, 12)] + development, _ = _household_splits( + children, seed=271828, validation_seed=validation_seed, partition="validation" + )[0] + children = children.loc[development].copy() + children["complete_household"] = children.groupby( + "source_household_id" + ).attendance_status.transform(lambda status: status.eq("complete").all()) + records = [] + complete = children.loc[children.attendance_status.eq("complete")] + for size, group in complete.groupby("childcare_household_size"): + for sample, rows in ( + ("all_observed_children", group), + ("children_in_complete_households", group.loc[group.complete_household]), + ( + "observed_children_with_unresolved_siblings", + group.loc[~group.complete_household], + ), + ): + if rows.empty: + continue + weights = rows.child_weight + records.append( + { + "household_size_capped_at_3": int(size), + "sample": sample, + "children": len(rows), + "households": int(rows.source_household_id.nunique()), + "child_weight": float(weights.sum()), + "mean_days": float( + np.average(rows.childcare_days_per_week, weights=weights) + ), + "mean_weekly_hours": float( + np.average( + rows.childcare_days_per_week * rows.childcare_hours_per_day, + weights=weights, + ) + ), + } + ) + support = {} + for name, columns in ( + ("legacy", NSECE_CHILDCARE_MATCH_COLUMNS), + ("household_size", match_columns), + ): + counts = complete.groupby(list(columns)).source_household_id.transform( + "nunique" + ) + support[name] = { + "children": len(complete), + "median_donor_households": float(counts.median()), + "children_in_cells_below_10_households": int((counts < 10).sum()), + "fraction_of_children_in_cells_below_10_households": float( + (counts < 10).mean() + ), + "definition": "Exact matching cells in the whole development pool; fold-training support can be smaller. Ten is a descriptive cutoff, not a tuned fallback rule.", + } + return { + "partition": "development only", + "observed_child_comparisons": records, + "donor_support": support, + "interpretation": "Descriptive differences under calendar selection; not a causal effect of missingness and not corrected population totals.", + } diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index 22e1b1636..6920cfc88 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -951,6 +951,168 @@ def test_undefined_sibling_metrics_do_not_pass_validation(): assert all(not check["passed"] for check in result["checks"]) +def _sibling_source(): + from microcosm.build.us_runtime.nsece_childcare import NSECEChildcareSource + + rows = [] + for household in range(100): + size = 1 if household % 2 else 3 + for child in range(size): + days = 5.0 if size == 1 else 0.0 + rows.append( + { + "donor_id": f"h{household}:c{child}", + "source_household_id": f"h{household}", + "age": 3, + "childcare_household_size": size, + "region": 1, + "parent_work_status": 2, + "income_band": 1, + "attendance_status": "complete", + "household_weight": 1.0, + "child_weight": 1.0, + MONTH: 22.0 if days else 0.0, + DAYS: days, + HOURS: 8.0 if days else 0.0, + } + ) + children = pd.DataFrame(rows) + return NSECEChildcareSource( + children, Weights(np.ones(len(children)), WeightKind.DESIGN), {} + ) + + +def test_reserved_households_are_excluded_from_every_development_split(): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + _household_splits, + ) + + children = _sibling_source().children + arguments = {"seed": 271828, "validation_seed": 20260916} + train, reserved = _household_splits(children, partition="validation", **arguments)[ + 0 + ] + heldout_ids = set(children.loc[reserved, "source_household_id"]) + assert heldout_ids + scored = set() + for training, evaluation in _household_splits( + children, partition="development", **arguments + ): + training_ids = set(children.loc[training, "source_household_id"]) + evaluation_ids = set(children.loc[evaluation, "source_household_id"]) + assert training_ids.isdisjoint(evaluation_ids | heldout_ids) + assert evaluation_ids.isdisjoint(heldout_ids | scored) + scored.update(evaluation_ids) + assert scored == set(children.loc[train, "source_household_id"]) + + +def test_household_size_changes_actual_validation_donors_and_not_only_rho(): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + assess_sibling_schedules, + ) + + source = _sibling_source() + settings = { + "fallback_match_columns": (), + "validation_seed": 20260916, + "partition": "validation", + } + legacy = assess_sibling_schedules(source, match_columns=("age",), **settings) + conditioned = assess_sibling_schedules( + source, match_columns=("age", "childcare_household_size"), **settings + ) + assert legacy["larger_households"]["coupled"]["mean_total_days"] > 0 + assert conditioned["larger_households"]["coupled"]["mean_total_days"] == 0 + assert conditioned["matching_counts"] == { + "age,childcare_household_size": 3 + * conditioned["larger_households"]["households"] + } + + +def test_reserved_outcomes_cannot_influence_development(monkeypatch): + from microcosm.build.us_runtime import nsece_childcare_sibling_validation as module + + source = _sibling_source() + settings = {"validation_seed": 20260916, "partition": "development"} + _, reserved = module._household_splits( + source.children, seed=271828, validation_seed=20260916, partition="validation" + )[0] + reserved_ids = set(source.children.loc[reserved, "source_household_id"]) + original_fit = module.fit_nsece_sibling_dependence + + def checked_fit(children, **kwargs): + assert set(children.source_household_id).isdisjoint(reserved_ids) + return original_fit(children, **kwargs) + + monkeypatch.setattr(module, "fit_nsece_sibling_dependence", checked_fit) + before = module.assess_sibling_schedules(source, **settings) + source.children.loc[reserved, [MONTH, DAYS, HOURS]] = [30, 7, 24] + assert module.assess_sibling_schedules(source, **settings) == before + + +def test_sibling_assessment_rejects_reconstructed_calendars(): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + assess_sibling_schedules, + ) + + source = _sibling_source() + source.children.loc[0, "attendance_status"] = "summary_bridge" + with pytest.raises(ValueError, match="original measured"): + assess_sibling_schedules(source) + + +def test_size_challenger_counts_missing_calendars_without_mutating_source(monkeypatch): + from microcosm.build.us_runtime import nsece_childcare_sibling_validation as module + + source = _sibling_source() + first_household = source.children.index[ + source.children.source_household_id.eq("h0") + ] + source.children.loc[first_household[1], "attendance_status"] = "missing_calendar" + source.children.loc[first_household[1], [MONTH, DAYS, HOURS]] = np.nan + source.children.loc[first_household[2], "age"] = 13 + before = source.children.copy(deep=True) + calls = [] + + def capture(candidate, **kwargs): + calls.append((candidate.children.copy(), kwargs)) + return {} + + monkeypatch.setattr(module, "assess_sibling_schedules", capture) + report = module.compare_household_size_matching(source, partition="development") + assert len(calls) == 2 + revised, settings = calls[1] + assert revised.loc[first_household, "childcare_household_size"].tolist() == [ + 2, + 2, + 2, + ] + assert "childcare_household_size" in settings["match_columns"] + assert settings["fallback_match_columns"][-2:] == ( + ("age", "childcare_household_size"), + ("age",), + ) + assert_frame_equal(source.children, before) + assert report["production_recipe_changed"] is False + + +@pytest.mark.parametrize( + "partition,validation_seed", [("unknown", 1), ("all", 1), ("validation", None)] +) +def test_sibling_partition_configuration_fails_closed(partition, validation_seed): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + _household_splits, + ) + + with pytest.raises(ValueError): + _household_splits( + _sibling_source().children, + seed=1, + validation_seed=validation_seed, + partition=partition, + ) + + @pytest.mark.parametrize( "irregular,shift,expected_days,expected_hours", [ diff --git a/tools/validate_us_childcare_household_size.py b/tools/validate_us_childcare_household_size.py new file mode 100644 index 000000000..49283845a --- /dev/null +++ b/tools/validate_us_childcare_household_size.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Compare the declared household-size challenger on separate survey partitions. + +Writes aggregate diagnostics only. It does not alter the population build, +publish artifacts, or turn previously inspected data into external evidence. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + +from microcosm.build.us_runtime import nsece_childcare_sibling_validation +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + attendance_recipe_identity, +) +from microcosm.build.us_runtime.nsece_childcare import load_nsece_childcare +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + compare_household_size_matching, +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument( + "--partition", choices=("development", "validation"), required=True + ) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error( + "Report path must be new; existing evidence will not be overwritten" + ) + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + report = compare_household_size_matching(source, partition=args.partition) + root = Path(__file__).resolve().parents[1] + plan = root / "experiments/us-childcare-attendance/household-size-plan.txt" + report["plan_sha256"] = hashlib.sha256(plan.read_bytes()).hexdigest() + report["recipe"] = attendance_recipe_identity() + report["diagnostic_code_sha256"] = { + path.name: hashlib.sha256(path.read_bytes()).hexdigest() + for path in (Path(__file__), Path(nsece_childcare_sibling_validation.__file__)) + } + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + for arm in ("legacy", "household_size"): + failures = [ + c["metric"] + for c in report[arm]["diagnostic_screen"]["checks"] + if not c["passed"] + ] + print(f"{arm}: {len(failures)}/15 screens failed") + print(json.dumps(report[arm]["larger_households"], indent=2)) + + +if __name__ == "__main__": + main() From d51b0e660cb1a4401fe070ad3a0427348639154d Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Wed, 16 Sep 2026 17:01:39 -0400 Subject: [PATCH 08/16] Evaluate partial pooling and expose remaining attendance selection gaps --- docs/us-childcare-attendance.md | 12 + experiments/us-childcare-attendance/README.md | 112 +- .../pooled-matching-plan.txt | 44 + .../pooled-matching-validation.json | 2517 +++++++++++++++++ .../pooled-moments-plan.txt | 26 + .../us_runtime/nsece_childcare_pooling.py | 234 ++ .../nsece_childcare_sibling_validation.py | 285 +- .../tests/test_us_childcare_pooling.py | 282 ++ .../tests/test_us_spine_blindness.py | 1 + tools/validate_us_childcare_pooling.py | 97 + 10 files changed, 3604 insertions(+), 6 deletions(-) create mode 100644 experiments/us-childcare-attendance/pooled-matching-plan.txt create mode 100644 experiments/us-childcare-attendance/pooled-matching-validation.json create mode 100644 experiments/us-childcare-attendance/pooled-moments-plan.txt create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py create mode 100644 packages/microcosm-build/tests/test_us_childcare_pooling.py create mode 100644 tools/validate_us_childcare_pooling.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index 9edd77149..c24fb5273 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -92,6 +92,18 @@ calendars. That selection is a separate limitation, not evidence that all population attendance should simply be scaled downward. The survey had already informed development, so the reserved comparison is not external validation. +A subsequent diagnostic model partially pools sparse cells and fits measured +sibling participation, days, and hours jointly. Its exploratory population-moment +fit passes 13 of 15 original household screens. The remaining two hours screens +require incompatible joint moments under its predicted marginal distributions; +changing sibling dependence alone cannot fix both. All 18 observed-child checks +are also reported, including three failures for children with unresolved siblings +(hours underpredicted by 40.7%). This remains experimental, has no production +source/target integration, and does not change population attendance values. +See the [plans and full comparison](../experiments/us-childcare-attendance/README.md) +for source-selection limits and reproducible commands. All inspected survey +partitions now count as development evidence. + ## ASEC target harmonization `harmonize_asec_childcare_predictors` resolves `PEPAR1` and `PEPAR2` against diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index 307131120..03bbde790 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,4 +1,114 @@ -# NSECE attendance household-model investigation — 2026-09-16 +# NSECE attendance pooled-model investigation — 2026-09-16 + +**PR #916 remains draft.** A partially pooled donor model improves conditional +child predictions and larger-family means. An exploratory population-moment +dependence fit passes 13 of the original 15 household screens, but still fails +two hours checks and all three checks for observed children with unresolved +siblings. Neither experimental model has replaced the production-stage recipe. + +- [Initial pooling and evaluation plan](pooled-matching-plan.txt) +- [Subsequent exploratory dependence-objective plan](pooled-moments-plan.txt) +- [All three models, full diagnostics and structural check](pooled-matching-validation.json) + +## What was tested + +The new donor model blends each sparse cell with broader empirical distributions +at the same exact age. It adds household size, work, income, and region in that +order. A cell's contribution is `effective_households / (effective_households + 10)`; +the strength ten was fixed before inspecting results. Survey-weight concentration +is measured at the donor-household level. Joint day/hour schedules, including +measured nonattendance, remain intact. + +The sibling fit uses every available observed pair, including families with +unresolved other siblings. Household weight is divided among the household's +observed pairs. Every fitting pair's entire household is excluded from its donor +distributions. The initial fit minimizes individual pair cross-product errors +for participation, days and hours. It improved mean errors but worsened several +correlations. A separately recorded exploratory alternative fits the three +weighted population cross-product moments instead. It retains one shared-rank +coefficient and the same fixed donor distributions; no per-outcome coefficients +or revised thresholds are selected from the evaluation results. + +Every component is refitted inside five household-separated folds. All 7,460 +measured child calendars are scored, alongside 4,450 observed pairs from 2,106 +households and the original complete-household diagnostics. Previously inspected +households, including the earlier reserved partition, are explicitly treated as +development data. This is not independent validation or evidence of release readiness. + +| Quantity | Existing model | Pooled, individual-pair fit | Pooled, population-moment fit | +| --- | ---: | ---: | ---: | +| Failed original household screens / 15 | 9 | 4 | 2 | +| Failed observed-child screens / 18 | 3 | 3 | 3 | +| 3+ household mean-days error | +25.35% | +12.01% | +12.01% | +| 3+ household mean-hours error | +23.36% | +15.91% | +15.91% | +| Conditional mean days prediction MSE | 5.761 | 4.634 | 4.634 | +| Conditional mean weekly-hours prediction MSE | 575.310 | 482.798 | 482.798 | + +Conditional-mean prediction errors improve approximately 20% for days and 16% +for hours. This is not a claim that random donor draws are more accurate: the +expected squared error of a single hours draw rises from 891.140 to 905.994. +The report contains both quantities. Marginals are identical across the pooled +fit objectives because the dependence coefficient changes only joint behavior. + +## What remains unresolved + +For the youngest pair in 3+ complete households, the population-moment model's +weekly-hours correlation is 0.456 versus 0.562 observed (gap 0.106; limit 0.10). +Its hours cross-product is 437.072 versus 337.347 (+29.6%; limit 20%). A change +to sibling dependence alone cannot satisfy both with these predicted marginals: + +| Existing screen | Required hours cross-product interval | +| --- | ---: | +| Correlation within 0.10 of observed | 440.651–553.157 | +| Cross-product within 20% of observed | 269.878–404.816 | + +The intervals do not overlap. Correlation equals `(E[XY] - E[X]E[Y]) / (SD[X]SD[Y])`. +Independent and coupled arms have the same marginal means and variances, so the +report can recover this relationship and test compatibility without choosing +another coefficient. This rules out a dependence-only fix **for these evaluated +marginals**; it does not rule out better marginal models or establish that the +selected complete families represent the whole population. + +The 761 observed children whose siblings have unresolved calendars remain a +material selection concern. Observed versus pooled predictions are 66.0% versus +46.7% participation, 2.819 versus 1.915 days/week, and 23.830 versus 14.135 hours/week. +Hours are underpredicted by 40.7%. Both pooled objectives share these failures; +better complete-family results do not waive them. Missing children are never +scored as zero, and these observed-subgroup differences do not identify their +unobserved schedules or a causal missingness effect. + +**Decision:** keep pooling experimental. The next substantive change must improve +the conditional marginal model and address calendar nonresponse using measured +information or explicit, tested assumptions. Further rho tuning, resplitting this +same survey, or lowering predictions to complete-household means would not close +the evidence gap. A population trial would also require consistent source bridge +and target integration, followed by new benefit/transport sensitivity evidence. + +Reproduce the complete comparison with new local output paths: + +```bash +uv run python tools/validate_us_childcare_pooling.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --compare-moment-fit --report /local/pooled-comparison.json +``` + +Synthetic tests cover exact-age support, household-cluster pooling, survey-weight +and row-order invariance, whole-household exclusions at both fit and evaluation +boundaries, inclusion of partially observed families, joint schedule preservation, +structural screen compatibility, and JSON-safe undefined metrics. Tests reside +directly in the build shard's tracked CI inventory; CI does not access survey data. +Production recipe hashes match the last verified population artifact exactly, +so no population rebuild or new state benefit estimate is claimed by this experiment. + +The 616-test attendance/source/architecture regression run passed. After making +undefined relative-error flags JSON-safe, all 96 focused source/pooling tests +passed again. Lint, formatting, tracked CI inventory and build-wheel source-byte +checks passed. The final real-source run reproduces both initial model arms +exactly and records the current diagnostic code hashes; only aggregate evidence +is committed. GitHub CI is separate and has not been monitored. + +## Previous hard household-size investigation — 2026-09-16 **PR #916 remains draft.** Adding household size improves larger-family means, but the proposed matcher still fails joint-schedule validation. It is retained diff --git a/experiments/us-childcare-attendance/pooled-matching-plan.txt b/experiments/us-childcare-attendance/pooled-matching-plan.txt new file mode 100644 index 000000000..fc0182d33 --- /dev/null +++ b/experiments/us-childcare-attendance/pooled-matching-plan.txt @@ -0,0 +1,44 @@ +Partially pooled attendance comparison specified before new results, 2026-09-16. + +The previous hard household-size model is rejected. All previously inspected +households, including the September 16 reserved partition, are now development +evidence. Do not call a new split of them independent or untouched validation. + +Compare the existing production matcher with one fixed proposed model. Start +with weighted exact-age empirical donor distributions, then successively add +under-13 household count (capped at 3), parent work, income band, and region. +At each level blend the local distribution with the preceding broader +distribution using lambda = effective_households / (effective_households + 10). +Effective households = squared total weight / sum of squared household weight +contributions to that donor cell. Ten is fixed before results, not optimized. +All days/hours are drawn jointly from real, complete schedules, including zeros; +exact age is never relaxed. Empty local cells inherit their broader distribution. + +Fit the shared-rank mixture to measured participation, days and weekly-hours +cross-products jointly. Use every pair of complete observed children in a +household, including families whose other children have unresolved calendars. +Divide household design weight among its observed pairs. Predict fitting pairs +with their entire household excluded from every donor level. Normalize each +outcome's squared-error contribution by its weighted training second moment; +fit one coefficient by weighted least squares and report its unconstrained value +and clipping. No fitting on held-out household outcomes. + +Evaluation: the same deterministic five whole-household folds, seed 271828, +for both models. Refit every model component solely inside each training fold. +Integrate the donor CDFs exactly. Retain all 15 previous joint-schedule screens. +Additionally score every observed child (not only complete households), with +child weights, overall, by count 1/2/3+, and by complete versus unresolved-sibling +household. Report participation, days, weekly hours, and squared prediction error. +Flag participation mean gaps over 0.05 and days/hour relative mean gaps over 20%. +Report observed-pair results separately from complete-household total results. + +This model does not identify missing calendars, summer patterns or noncalendar +irregular care. Incomplete-family subgroup errors remain visible and cannot be +removed from acceptance merely because complete-family totals look good. Do not +scale the population to complete-household means or claim a missingness correction. + +Do not select a favorable seed or tune smoothing/rho to the evaluation folds. +Any adoption remains draft and requires source/target/bridge consistency, +content-bound receipts, a rebuild from the original parent, population and +noncalendar sensitivity checks. Failed diagnostics and source-selection +uncertainty remain unresolved; publication is not authorized by this experiment. diff --git a/experiments/us-childcare-attendance/pooled-matching-validation.json b/experiments/us-childcare-attendance/pooled-matching-validation.json new file mode 100644 index 000000000..9123cd9fc --- /dev/null +++ b/experiments/us-childcare-attendance/pooled-matching-validation.json @@ -0,0 +1,2517 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "plan_sha256": "a25a5ea699fb8732ce7f0fb5ded9f6ff191612ec7f850ac7c53d0b66fe3508fe", + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "1.819.0" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "affc8ac5a3ce853e2ef8b062c51a1801a15573cbcbf6eacc2c093334a1d12c11", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "diagnostic_code_sha256": { + "validate_us_childcare_pooling.py": "717816151c9d167956f3222104012dbc1877d5bd914fa861a5c5cd1d5d59ab0b", + "nsece_childcare_pooling.py": "0e5375a99440e89040cca8051df8178bf714f96938d2d9501453aab65420005a", + "nsece_childcare_sibling_validation.py": "6ed9a3c9c2d881782c8466133da21c952c9542f394b7501afa87e9a3ba3163a1" + }, + "production_ready": false, + "production_recipe_changed": false, + "interpretation": "five household-separated folds on previously inspected development data; no untouched evaluation claim; calendar selection remains unidentified", + "moment_plan_sha256": "1f56373d30787ad399566205718f99d07ae4f6962ab56ac350bba662e7ec9ae0", + "legacy": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,region,parent_work_status,income_band": 7278, + "age,parent_work_status,income_band": 150, + "age,parent_work_status": 32 + }, + "training_rho": [ + 0.7504775690168417, + 0.8095401305423818, + 0.7176223341350483, + 0.8946850037821993, + 0.8022505236076816 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.2599357218299179, + "correlation": 0.14356484664250233 + }, + "days": { + "joint_product": 4.562554958882675, + "correlation": 0.1514064619097721 + }, + "weekly_hours": { + "joint_product": 274.5482618143516, + "correlation": 0.1290575794049498 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3228241594928079, + "correlation": 0.39662161163709453 + }, + "days": { + "joint_product": 6.168391228908872, + "correlation": 0.44224735414975214 + }, + "weekly_hours": { + "joint_product": 393.50536137310974, + "correlation": 0.35635277128820114 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.26594831103314626, + "correlation": 0.1768906778018396 + }, + "days": { + "joint_product": 4.755017625871223, + "correlation": 0.1796772530434437 + }, + "weekly_hours": { + "joint_product": 288.78160178706247, + "correlation": 0.15022942733210107 + } + }, + "coupled": { + "participation": { + "joint_product": 0.32461663032314914, + "correlation": 0.41320572283269663 + }, + "days": { + "joint_product": 6.243213912824172, + "correlation": 0.44765187465853035 + }, + "weekly_hours": { + "joint_product": 406.53469377347056, + "correlation": 0.3715598543103152 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 4.742171858563125, + "sd_total_weekly_hours": 43.69183090611876, + "all_children_attend": 0.10870430564775237 + }, + "coupled": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 5.826012333228911, + "sd_total_weekly_hours": 52.0732028760817, + "all_children_attend": 0.19850142013240785 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.005764539166296878, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.13764128251529073, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.16710441607001986, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.04245779938404666, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.07023724634135614, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.051720923806702845, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.11411618300267751, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.19079301720982428, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.22780056599436455, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.20509336751041, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.2534916563112233, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.23360354492760613, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.010530264051684455, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.07370280213290151, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.0128154922623471, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + }, + "matching_count_universe": "every held-out child with an observed complete calendar", + "observed_child_validation": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.4654844290416031, + "days": 1.8785272940115059, + "weekly_hours": 14.081114700859386 + }, + "conditional_mean_squared_error": { + "participation": 0.2627611011205091, + "days": 5.761259738909554, + "weekly_hours": 575.3095532746996 + }, + "expected_draw_squared_error": { + "participation": 0.4131049741549481, + "days": 8.904698658118924, + "weekly_hours": 891.1402112413062 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.4773376030928158, + "days": 1.9290793011901837, + "weekly_hours": 14.82135847302154 + }, + "conditional_mean_squared_error": { + "participation": 0.2595087340797617, + "days": 5.761185130166251, + "weekly_hours": 647.1235859031791 + }, + "expected_draw_squared_error": { + "participation": 0.41350182254230894, + "days": 9.02534933769786, + "weekly_hours": 977.847851925658 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.4820542836393625, + "days": 1.9588226354748015, + "weekly_hours": 14.72400423392797 + }, + "conditional_mean_squared_error": { + "participation": 0.2603267026889349, + "days": 5.819759954687851, + "weekly_hours": 514.7834898967403 + }, + "expected_draw_squared_error": { + "participation": 0.410695073394001, + "days": 9.00784605459559, + "weekly_hours": 830.9671222797516 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.39657324091775625, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.43273585199154396, + "days": 1.7260541997125938, + "weekly_hours": 12.5572146611429 + }, + "conditional_mean_squared_error": { + "participation": 0.26893753384764646, + "days": 5.68351645580513, + "weekly_hours": 590.9320595275229 + }, + "expected_draw_squared_error": { + "participation": 0.4159518190529244, + "days": 8.658497269322753, + "weekly_hours": 892.8371090168542 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.91535441, + "observed": { + "participation": 0.44408939246351814, + "days": 1.7729192348218488, + "weekly_hours": 13.20243633175764 + }, + "expected": { + "participation": 0.46248243745239037, + "days": 1.8656545413639731, + "weekly_hours": 13.968992525952057 + }, + "conditional_mean_squared_error": { + "participation": 0.2618570243099771, + "days": 5.617466178489852, + "weekly_hours": 539.6353259350994 + }, + "expected_draw_squared_error": { + "participation": 0.41271493741040616, + "days": 8.768285612163377, + "weekly_hours": 858.5825009186116 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.829753088509108 + }, + "expected": { + "participation": 0.49321802361085093, + "days": 1.9974509126518023, + "weekly_hours": 15.116944032385828 + }, + "conditional_mean_squared_error": { + "participation": 0.27111332296312274, + "days": 7.0896819505766215, + "weekly_hours": 904.8822813138923 + }, + "expected_draw_squared_error": { + "participation": 0.41670828902735096, + "days": 10.164936736093896, + "weekly_hours": 1191.9213131728277 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.00033597797098450233, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.0018433103093970732, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.01118701852984847, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.0468732883160774, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.08984054071404815, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.12359714450611524, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.005468050673460845, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.019639868618916312, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.056117507490689326, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.03616261107378771, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.0835079214816311, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.02721772181951925, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.01839304498887223, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.05230656011887808, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.05806172246788369, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.1664824027277681, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.2913798359523947, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.36562733250998997, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "all_observed_sibling_pairs": { + "household_weight": 8945945.325594446, + "observed": { + "participation": { + "joint_product": 0.3302177486685269, + "correlation": 0.569690244749424 + }, + "days": { + "joint_product": 6.027208927705104, + "correlation": 0.5724522456066773 + }, + "weekly_hours": { + "joint_product": 426.22614842124585, + "correlation": 0.5408408373849536 + } + }, + "independent": { + "participation": { + "joint_product": 0.24552027543447916, + "correlation": 0.13532417830267465 + }, + "days": { + "joint_product": 4.226646089372958, + "correlation": 0.14391630402285718 + }, + "weekly_hours": { + "joint_product": 244.5514800119603, + "correlation": 0.1148066000332254 + } + }, + "coupled": { + "participation": { + "joint_product": 0.30897285504551686, + "correlation": 0.3919191231187823 + }, + "days": { + "joint_product": 5.812085743353472, + "correlation": 0.43785061514617757 + }, + "weekly_hours": { + "joint_product": 358.34194451182583, + "correlation": 0.3402179517218739 + } + }, + "observed_pairs": 4450, + "households": 2106, + "interpretation": "all observed pairs including households with unresolved other siblings; household weight divided among observed pairs" + }, + "dependence_only_screen_compatibility": { + "comparisons": [ + { + "population": "youngest_pairs", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.7265858962845724, + "predicted_product_of_marginal_standard_deviations": 5.5213565659851636, + "joint_product_required_by_correlation_screen": [ + 6.376222171276775, + 7.480493484473808 + ], + "joint_product_allowed_by_product_screen": [ + 4.733729256035931, + 7.100593884053898 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 6.376222171276775, + 7.100593884053898 + ] + }, + { + "population": "youngest_pairs", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 207.00475068732095, + "predicted_product_of_marginal_standard_deviations": 523.3595069615889, + "joint_product_required_by_correlation_screen": [ + 428.62509548246067, + 533.2969968747784 + ], + "joint_product_allowed_by_product_screen": [ + 294.14439665098314, + 441.21659497647465 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 428.62509548246067, + 441.21659497647465 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.757180520187241, + "predicted_product_of_marginal_standard_deviations": 5.553497110971067, + "joint_product_required_by_correlation_screen": [ + 6.32160809434748, + 7.432307516541693 + ], + "joint_product_allowed_by_product_screen": [ + 4.067900983751674, + 6.101851475627511 + ], + "screen_intervals_overlap": false, + "overlap_interval": null + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 208.85594575675282, + "predicted_product_of_marginal_standard_deviations": 532.0239679382117, + "joint_product_required_by_correlation_screen": [ + 454.8387550505237, + 561.243548638166 + ], + "joint_product_allowed_by_product_screen": [ + 269.8776408426022, + 404.8164612639033 + ], + "screen_intervals_overlap": false, + "overlap_interval": null + } + ], + "interpretation": "disjoint screen intervals rule out a dependence-only fix for these conditional marginals; overlapping intervals do not establish that a feasible joint distribution exists" + } + }, + "pooled": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region" + ], + "fallback_match_columns": [], + "matching_counts": { + "partially_pooled": 7460 + }, + "training_rho": [ + 0.5276809642639594, + 0.4355228994352012, + 0.43533241705543085, + 0.5208424069481076, + 0.5321513856951827 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.24236105200931968, + "correlation": 0.09396101556276153 + }, + "days": { + "joint_product": 4.135970938921813, + "correlation": 0.11086235305220805 + }, + "weekly_hours": { + "joint_product": 234.25280025856392, + "correlation": 0.09296236405577511 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3100628328252084, + "correlation": 0.36716178386071613 + }, + "days": { + "joint_product": 5.7917816408332525, + "correlation": 0.4197512009637115 + }, + "weekly_hours": { + "joint_product": 357.67604657412755, + "correlation": 0.34249430439785367 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.20749391246494672, + "correlation": 0.07367636621560582 + }, + "days": { + "joint_product": 3.5642905999293837, + "correlation": 0.09515516766062374 + }, + "weekly_hours": { + "joint_product": 216.49341419707955, + "correlation": 0.063874755675981 + } + }, + "coupled": { + "participation": { + "joint_product": 0.2737895744968245, + "correlation": 0.34410567515623863 + }, + "days": { + "joint_product": 5.177923531761666, + "correlation": 0.399302222570031 + }, + "weekly_hours": { + "joint_product": 357.8613809895804, + "correlation": 0.3151807708126129 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.051031204266368, + "mean_total_weekly_hours": 38.34794143790316, + "sd_total_days": 4.395160013019163, + "sd_total_weekly_hours": 44.20739654038455, + "all_children_attend": 0.06527654820049239 + }, + "coupled": { + "mean_total_days": 5.051031204266368, + "mean_total_weekly_hours": 38.34794143790316, + "sd_total_days": 5.603842063134159, + "sd_total_weekly_hours": 54.68976225539357, + "all_children_attend": 0.15764122980318407 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.01852586583389637, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.16013743570133138, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.18096288296036733, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.02118920156691112, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.027209627253847346, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.0008938679803782157, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.16246583509117685, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.24717210070752654, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.0182988332201261, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.06081075815626243, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.12005018174031427, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.15906022067672387, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.028005490571213053, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.02715848591564965, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.028044698066876678, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + }, + "model": { + "name": "partially_pooled_empirical_schedules", + "levels": [ + [ + "age" + ], + [ + "age", + "childcare_household_size" + ], + [ + "age", + "childcare_household_size", + "parent_work_status" + ], + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band" + ], + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region" + ] + ], + "strength": 10.0, + "dependence_objective": "pair_squared_error", + "dependence_fits": [ + { + "rho": 0.5276809642639594, + "unconstrained_rho": 0.5276809642639594, + "households": 1730, + "households_with_unresolved_siblings": 140, + "observed_pairs": 3670, + "outcome_second_moments": [ + 0.4359359926938946, + 8.277205060093346, + 593.7870077076072 + ], + "objective": "pair_squared_error", + "normalized_mean_residual": [ + 0.2502298693914833, + 0.27527547268127456, + 0.2850732476726967 + ], + "normalized_mean_shared_increment": [ + 0.30253311517140813, + 0.38487878025611877, + 0.36627648825439335 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.4355228994352012, + "unconstrained_rho": 0.4355228994352012, + "households": 1697, + "households_with_unresolved_siblings": 123, + "observed_pairs": 3594, + "outcome_second_moments": [ + 0.44126287158160343, + 8.359355045936002, + 694.2809842648821 + ], + "objective": "pair_squared_error", + "normalized_mean_residual": [ + 0.22454696976548358, + 0.25665784026502875, + 0.3085026964880856 + ], + "normalized_mean_shared_increment": [ + 0.3121975302394453, + 0.3956058631862616, + 0.37836062350370525 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.43533241705543085, + "unconstrained_rho": 0.43533241705543085, + "households": 1633, + "households_with_unresolved_siblings": 135, + "observed_pairs": 3453, + "outcome_second_moments": [ + 0.4338366370776943, + 8.256340131249516, + 694.144636825422 + ], + "objective": "pair_squared_error", + "normalized_mean_residual": [ + 0.22762977893389097, + 0.2491739115147974, + 0.31614054777338935 + ], + "normalized_mean_shared_increment": [ + 0.3218355084453624, + 0.40226719431104246, + 0.37196853323063156 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.5208424069481076, + "unconstrained_rho": 0.5208424069481076, + "households": 1668, + "households_with_unresolved_siblings": 125, + "observed_pairs": 3499, + "outcome_second_moments": [ + 0.4428268423383291, + 8.341086892139936, + 679.4142064233605 + ], + "objective": "pair_squared_error", + "normalized_mean_residual": [ + 0.24679819920578627, + 0.27437897292653474, + 0.337362053609579 + ], + "normalized_mean_shared_increment": [ + 0.3146404967452197, + 0.40232577509104867, + 0.3772940863523165 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.5321513856951827, + "unconstrained_rho": 0.5321513856951827, + "households": 1696, + "households_with_unresolved_siblings": 137, + "observed_pairs": 3584, + "outcome_second_moments": [ + 0.4413586153564347, + 8.485578051981562, + 676.0109573424492 + ], + "objective": "pair_squared_error", + "normalized_mean_residual": [ + 0.2543497335812991, + 0.28684691450840843, + 0.35816569978000246 + ], + "normalized_mean_shared_increment": [ + 0.3106084542826501, + 0.4028661500770573, + 0.38060896199156885 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + } + ] + }, + "matching_count_universe": "every held-out child with an observed complete calendar", + "observed_child_validation": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.46964890773471457, + "days": 1.8870313262922465, + "weekly_hours": 14.121321880052811 + }, + "conditional_mean_squared_error": { + "participation": 0.21786002566014795, + "days": 4.634484500691751, + "weekly_hours": 482.79806934933634 + }, + "expected_draw_squared_error": { + "participation": 0.4189867368126556, + "days": 8.871248441333941, + "weekly_hours": 905.9935560782225 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5309528476583814, + "days": 2.1450855821675643, + "weekly_hours": 16.7286558904403 + }, + "conditional_mean_squared_error": { + "participation": 0.21998157968908702, + "days": 4.8634925946342795, + "weekly_hours": 551.1632871152738 + }, + "expected_draw_squared_error": { + "participation": 0.4281854166170082, + "days": 9.372662082607494, + "weekly_hours": 1034.4761146276157 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.48651284376374204, + "days": 1.9627422616863737, + "weekly_hours": 14.1440528078623 + }, + "conditional_mean_squared_error": { + "participation": 0.21802706174990996, + "days": 4.583635321783541, + "weekly_hours": 414.2968731100742 + }, + "expected_draw_squared_error": { + "participation": 0.4176700262900522, + "days": 8.771188666124276, + "weekly_hours": 779.6025285290256 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.39657324091775625, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.3918312548530712, + "days": 1.5531811863063212, + "weekly_hours": 11.73540600582258 + }, + "conditional_mean_squared_error": { + "participation": 0.2157210644872454, + "days": 4.495213713500396, + "weekly_hours": 512.1441841789803 + }, + "expected_draw_squared_error": { + "participation": 0.41242723371549633, + "days": 8.551318544556374, + "weekly_hours": 958.0235058267347 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.91535441, + "observed": { + "participation": 0.44408939246351814, + "days": 1.7729192348218488, + "weekly_hours": 13.20243633175764 + }, + "expected": { + "participation": 0.46990822362498935, + "days": 1.8840103414261442, + "weekly_hours": 14.119885124808388 + }, + "conditional_mean_squared_error": { + "participation": 0.21582355854647822, + "days": 4.473903862652206, + "weekly_hours": 447.4809381900267 + }, + "expected_draw_squared_error": { + "participation": 0.4175485380163647, + "days": 8.708831987458572, + "weekly_hours": 872.7199874173884 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.829753088509108 + }, + "expected": { + "participation": 0.46725324420518616, + "days": 1.914940388322065, + "weekly_hours": 14.13459519753524 + }, + "conditional_mean_squared_error": { + "participation": 0.23667372037103993, + "days": 6.117992424918431, + "weekly_hours": 809.0718005751868 + }, + "expected_draw_squared_error": { + "participation": 0.4322733904027592, + "days": 10.371716363808677, + "weekly_hours": 1213.3880426008857 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.0045004566640959864, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.006378622560794653, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.008363564451150505, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.006741956249488146, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.01207344476879113, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.010816598381105233, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.009926610797840407, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.02168017950925578, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.014518982739467643, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0047419860646850664, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.02501084894127077, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.0400086844711946, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.025818831161471212, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.06265999286507735, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.06949087047243553, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.19244718213343287, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.3206514545518074, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.4068509587557979, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "all_observed_sibling_pairs": { + "household_weight": 8945945.325594446, + "observed": { + "participation": { + "joint_product": 0.3302177486685269, + "correlation": 0.569690244749424 + }, + "days": { + "joint_product": 6.027208927705104, + "correlation": 0.5724522456066773 + }, + "weekly_hours": { + "joint_product": 426.22614842124585, + "correlation": 0.5408408373849536 + } + }, + "independent": { + "participation": { + "joint_product": 0.2260919090368316, + "correlation": 0.09520635050450324 + }, + "days": { + "joint_product": 3.8285744975466867, + "correlation": 0.10968106727006208 + }, + "weekly_hours": { + "joint_product": 212.90904376258283, + "correlation": 0.08730651835284241 + } + }, + "coupled": { + "participation": { + "joint_product": 0.29327458485478475, + "correlation": 0.368453127817019 + }, + "days": { + "joint_product": 5.461991300173491, + "correlation": 0.41927549225218075 + }, + "weekly_hours": { + "joint_product": 333.74040670723997, + "correlation": 0.3351110984736457 + } + }, + "observed_pairs": 4450, + "households": 2106, + "interpretation": "all observed pairs including households with unresolved other siblings; household weight divided among observed pairs" + }, + "dependence_only_screen_compatibility": { + "comparisons": [ + { + "population": "youngest_pairs", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.5416889767723654, + "predicted_product_of_marginal_standard_deviations": 5.360538954730503, + "joint_product_required_by_correlation_screen": [ + 6.11415070754784, + 7.186258498493941 + ], + "joint_product_allowed_by_product_screen": [ + 4.733729256035931, + 7.100593884053898 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 6.11415070754784, + 7.100593884053898 + ] + }, + { + "population": "youngest_pairs", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 188.27184591178198, + "predicted_product_of_marginal_standard_deviations": 494.61903011840917, + "joint_product_required_by_correlation_screen": [ + 397.7218292195747, + 496.6456352432566 + ], + "joint_product_allowed_by_product_screen": [ + 294.14439665098314, + 441.21659497647465 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 397.7218292195747, + 441.21659497647465 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.0594508860714296, + "predicted_product_of_marginal_standard_deviations": 5.305436649100271, + "joint_product_required_by_correlation_screen": [ + 5.509332062571049, + 6.570419392391103 + ], + "joint_product_allowed_by_product_screen": [ + 4.067900983751674, + 6.101851475627511 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 5.509332062571049, + 6.101851475627511 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 180.56174604989343, + "predicted_product_of_marginal_standard_deviations": 562.5331598833434, + "joint_product_required_by_correlation_screen": [ + 440.650567847255, + 553.1571998239236 + ], + "joint_product_allowed_by_product_screen": [ + 269.8776408426022, + 404.8164612639033 + ], + "screen_intervals_overlap": false, + "overlap_interval": null + } + ], + "interpretation": "disjoint screen intervals rule out a dependence-only fix for these conditional marginals; overlapping intervals do not establish that a feasible joint distribution exists" + } + }, + "pooled_moments": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region" + ], + "fallback_match_columns": [], + "matching_counts": { + "partially_pooled": 7460 + }, + "training_rho": [ + 0.7652583390107988, + 0.7261225249617242, + 0.7209478813856636, + 0.7820313193382383, + 0.81974924157628 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.24236105200931968, + "correlation": 0.09396101556276153 + }, + "days": { + "joint_product": 4.135970938921813, + "correlation": 0.11086235305220805 + }, + "weekly_hours": { + "joint_product": 234.25280025856392, + "correlation": 0.09296236405577511 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3478032040042286, + "correlation": 0.5194576064772242 + }, + "days": { + "joint_product": 6.714922913449467, + "correlation": 0.5919617343470333 + }, + "weekly_hours": { + "joint_product": 426.7255628007384, + "correlation": 0.4820957188644195 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.20749391246494672, + "correlation": 0.07367636621560582 + }, + "days": { + "joint_product": 3.5642905999293837, + "correlation": 0.09515516766062374 + }, + "weekly_hours": { + "joint_product": 216.49341419707955, + "correlation": 0.063874755675981 + } + }, + "coupled": { + "participation": { + "joint_product": 0.31038475496885254, + "correlation": 0.49338255183709395 + }, + "days": { + "joint_product": 6.0717725199533, + "correlation": 0.5677801532872357 + }, + "weekly_hours": { + "joint_product": 437.0722821479231, + "correlation": 0.45599184970931156 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.051031204266368, + "mean_total_weekly_hours": 38.34794143790316, + "sd_total_days": 4.395160013019163, + "sd_total_weekly_hours": 44.20739654038455, + "all_children_attend": 0.06527654820049239 + }, + "coupled": { + "mean_total_days": 5.051031204266368, + "mean_total_weekly_hours": 38.34794143790316, + "sd_total_days": 6.177046168973302, + "sd_total_weekly_hours": 59.820431157004805, + "all_children_attend": 0.20892405597176808 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.01921450534512381, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.012073097681990386, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.04136146849380151, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.13482162586925894, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.1605879769508426, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.03748904845240625, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.006012095626027891, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.1063610218108279, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.1940846238304513, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.2956161341363792, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.12005018174031427, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.15906022067672387, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.07141759048294442, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.0641077309532437, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.023238128101707334, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + }, + "model": { + "name": "partially_pooled_empirical_schedules", + "levels": [ + [ + "age" + ], + [ + "age", + "childcare_household_size" + ], + [ + "age", + "childcare_household_size", + "parent_work_status" + ], + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band" + ], + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region" + ] + ], + "strength": 10.0, + "dependence_objective": "population_moments", + "dependence_fits": [ + { + "rho": 0.7652583390107988, + "unconstrained_rho": 0.7652583390107988, + "households": 1730, + "households_with_unresolved_siblings": 140, + "observed_pairs": 3670, + "outcome_second_moments": [ + 0.4359359926938946, + 8.277205060093346, + 593.7870077076072 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2502298693914833, + 0.27527547268127456, + 0.2850732476726967 + ], + "normalized_mean_shared_increment": [ + 0.30253311517140813, + 0.38487878025611877, + 0.36627648825439335 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.7261225249617242, + "unconstrained_rho": 0.7261225249617242, + "households": 1697, + "households_with_unresolved_siblings": 123, + "observed_pairs": 3594, + "outcome_second_moments": [ + 0.44126287158160343, + 8.359355045936002, + 694.2809842648821 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.22454696976548358, + 0.25665784026502875, + 0.3085026964880856 + ], + "normalized_mean_shared_increment": [ + 0.3121975302394453, + 0.3956058631862616, + 0.37836062350370525 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.7209478813856636, + "unconstrained_rho": 0.7209478813856636, + "households": 1633, + "households_with_unresolved_siblings": 135, + "observed_pairs": 3453, + "outcome_second_moments": [ + 0.4338366370776943, + 8.256340131249516, + 694.144636825422 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.22762977893389097, + 0.2491739115147974, + 0.31614054777338935 + ], + "normalized_mean_shared_increment": [ + 0.3218355084453624, + 0.40226719431104246, + 0.37196853323063156 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.7820313193382383, + "unconstrained_rho": 0.7820313193382383, + "households": 1668, + "households_with_unresolved_siblings": 125, + "observed_pairs": 3499, + "outcome_second_moments": [ + 0.4428268423383291, + 8.341086892139936, + 679.4142064233605 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.24679819920578627, + 0.27437897292653474, + 0.337362053609579 + ], + "normalized_mean_shared_increment": [ + 0.3146404967452197, + 0.40232577509104867, + 0.3772940863523165 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.81974924157628, + "unconstrained_rho": 0.81974924157628, + "households": 1696, + "households_with_unresolved_siblings": 137, + "observed_pairs": 3584, + "outcome_second_moments": [ + 0.4413586153564347, + 8.485578051981562, + 676.0109573424492 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2543497335812991, + 0.28684691450840843, + 0.35816569978000246 + ], + "normalized_mean_shared_increment": [ + 0.3106084542826501, + 0.4028661500770573, + 0.38060896199156885 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + } + ] + }, + "matching_count_universe": "every held-out child with an observed complete calendar", + "observed_child_validation": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.46964890773471457, + "days": 1.8870313262922465, + "weekly_hours": 14.121321880052811 + }, + "conditional_mean_squared_error": { + "participation": 0.21786002566014795, + "days": 4.634484500691751, + "weekly_hours": 482.79806934933634 + }, + "expected_draw_squared_error": { + "participation": 0.4189867368126556, + "days": 8.871248441333941, + "weekly_hours": 905.9935560782225 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5309528476583814, + "days": 2.1450855821675643, + "weekly_hours": 16.7286558904403 + }, + "conditional_mean_squared_error": { + "participation": 0.21998157968908702, + "days": 4.8634925946342795, + "weekly_hours": 551.1632871152738 + }, + "expected_draw_squared_error": { + "participation": 0.4281854166170082, + "days": 9.372662082607494, + "weekly_hours": 1034.4761146276157 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.48651284376374204, + "days": 1.9627422616863737, + "weekly_hours": 14.1440528078623 + }, + "conditional_mean_squared_error": { + "participation": 0.21802706174990996, + "days": 4.583635321783541, + "weekly_hours": 414.2968731100742 + }, + "expected_draw_squared_error": { + "participation": 0.4176700262900522, + "days": 8.771188666124276, + "weekly_hours": 779.6025285290256 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.39657324091775625, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.3918312548530712, + "days": 1.5531811863063212, + "weekly_hours": 11.73540600582258 + }, + "conditional_mean_squared_error": { + "participation": 0.2157210644872454, + "days": 4.495213713500396, + "weekly_hours": 512.1441841789803 + }, + "expected_draw_squared_error": { + "participation": 0.41242723371549633, + "days": 8.551318544556374, + "weekly_hours": 958.0235058267347 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.91535441, + "observed": { + "participation": 0.44408939246351814, + "days": 1.7729192348218488, + "weekly_hours": 13.20243633175764 + }, + "expected": { + "participation": 0.46990822362498935, + "days": 1.8840103414261442, + "weekly_hours": 14.119885124808388 + }, + "conditional_mean_squared_error": { + "participation": 0.21582355854647822, + "days": 4.473903862652206, + "weekly_hours": 447.4809381900267 + }, + "expected_draw_squared_error": { + "participation": 0.4175485380163647, + "days": 8.708831987458572, + "weekly_hours": 872.7199874173884 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.829753088509108 + }, + "expected": { + "participation": 0.46725324420518616, + "days": 1.914940388322065, + "weekly_hours": 14.13459519753524 + }, + "conditional_mean_squared_error": { + "participation": 0.23667372037103993, + "days": 6.117992424918431, + "weekly_hours": 809.0718005751868 + }, + "expected_draw_squared_error": { + "participation": 0.4322733904027592, + "days": 10.371716363808677, + "weekly_hours": 1213.3880426008857 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.0045004566640959864, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.006378622560794653, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.008363564451150505, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.006741956249488146, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.01207344476879113, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.010816598381105233, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.009926610797840407, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.02168017950925578, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.014518982739467643, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0047419860646850664, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.02501084894127077, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.0400086844711946, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.025818831161471212, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.06265999286507735, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.06949087047243553, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.19244718213343287, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.3206514545518074, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.4068509587557979, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "all_observed_sibling_pairs": { + "household_weight": 8945945.325594446, + "observed": { + "participation": { + "joint_product": 0.3302177486685269, + "correlation": 0.569690244749424 + }, + "days": { + "joint_product": 6.027208927705104, + "correlation": 0.5724522456066773 + }, + "weekly_hours": { + "joint_product": 426.22614842124585, + "correlation": 0.5408408373849536 + } + }, + "independent": { + "participation": { + "joint_product": 0.2260919090368316, + "correlation": 0.09520635050450324 + }, + "days": { + "joint_product": 3.8285744975466867, + "correlation": 0.10968106727006208 + }, + "weekly_hours": { + "joint_product": 212.90904376258283, + "correlation": 0.08730651835284241 + } + }, + "coupled": { + "participation": { + "joint_product": 0.33074016165677533, + "correlation": 0.5208339015307502 + }, + "days": { + "joint_product": 6.372410806864982, + "correlation": 0.5918345128361356 + }, + "weekly_hours": { + "joint_product": 401.34477075365476, + "correlation": 0.4737561541826404 + } + }, + "observed_pairs": 4450, + "households": 2106, + "interpretation": "all observed pairs including households with unresolved other siblings; household weight divided among observed pairs" + }, + "dependence_only_screen_compatibility": { + "comparisons": [ + { + "population": "youngest_pairs", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.5416889767723654, + "predicted_product_of_marginal_standard_deviations": 5.360538954730504, + "joint_product_required_by_correlation_screen": [ + 6.114150707547841, + 7.186258498493942 + ], + "joint_product_allowed_by_product_screen": [ + 4.733729256035931, + 7.100593884053898 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 6.114150707547841, + 7.100593884053898 + ] + }, + { + "population": "youngest_pairs", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 188.27184591178198, + "predicted_product_of_marginal_standard_deviations": 494.6190301184091, + "joint_product_required_by_correlation_screen": [ + 397.7218292195747, + 496.6456352432565 + ], + "joint_product_allowed_by_product_screen": [ + 294.14439665098314, + 441.21659497647465 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 397.7218292195747, + 441.21659497647465 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.0594508860714296, + "predicted_product_of_marginal_standard_deviations": 5.305436649100272, + "joint_product_required_by_correlation_screen": [ + 5.509332062571049, + 6.570419392391104 + ], + "joint_product_allowed_by_product_screen": [ + 4.067900983751674, + 6.101851475627511 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 5.509332062571049, + 6.101851475627511 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 180.56174604989343, + "predicted_product_of_marginal_standard_deviations": 562.5331598833434, + "joint_product_required_by_correlation_screen": [ + 440.650567847255, + 553.1571998239236 + ], + "joint_product_allowed_by_product_screen": [ + 269.8776408426022, + 404.8164612639033 + ], + "screen_intervals_overlap": false, + "overlap_interval": null + } + ], + "interpretation": "disjoint screen intervals rule out a dependence-only fix for these conditional marginals; overlapping intervals do not establish that a feasible joint distribution exists" + } + } +} diff --git a/experiments/us-childcare-attendance/pooled-moments-plan.txt b/experiments/us-childcare-attendance/pooled-moments-plan.txt new file mode 100644 index 000000000..313160165 --- /dev/null +++ b/experiments/us-childcare-attendance/pooled-moments-plan.txt @@ -0,0 +1,26 @@ +Exploratory dependence-objective comparison, specified after the first pooled +results and before the alternative results, 2026-09-16. + +The first pooled model reduces mean errors and original-screen failures (9 to 4) +but worsens all four screened sibling intensity correlations. Its rowwise +cross-product squared-error fit targets individual conditional products, while +the required diagnostics target weighted population joint moments. Retain that +model as a comparator; do not describe its lower screen count as overall success. + +Test one alternative objective with the SAME donor pooling, fixed strength 10, +all observed sibling pairs, household/pair weights, leave-household-out donor +exclusion, training-only normalization scales, and five outer folds. Set rho to +minimize the sum of squared normalized WEIGHTED MEAN cross-product residuals +for participation, days and weekly hours. This has one analytic least-squares +coefficient, clipped to [0, 1]; expose the raw value and moments. Do not choose +separate coefficients for outcomes or alter thresholds after seeing results. + +Compare all three arms (original, pooled rowwise objective, pooled moment +objective) including all original screens and all 18 observed-child checks. +This ablation is motivated by already viewed outcomes. It is exploratory model +development, not a confirmatory test, and the previous held-out sample is not +reused under an independent-evidence label. Reproduce the first pooled arm +numerically to ensure the objective is the only behavioral change. + +Calendar selection remains unidentified. An improvement in correlation cannot +waive incomplete-family mean errors or authorize default population replacement. diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py new file mode 100644 index 000000000..f502e147d --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py @@ -0,0 +1,234 @@ +"""Experimental partial pooling of joint childcare schedule distributions. + +Sparse cells borrow strength from broader cells, always retaining exact age. +Only measured complete calendars are donors; missing outcomes are not zeros. +This module does not change the production build or correct calendar selection. +""" + +from __future__ import annotations + +from itertools import combinations + +import numpy as np + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + _ids, + _validate_attendance, +) + +POOLED_CHILDCARE_MATCH_COLUMNS = ( + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region", +) +POOLED_CHILDCARE_LEVELS = tuple( + POOLED_CHILDCARE_MATCH_COLUMNS[:n] + for n in range(1, len(POOLED_CHILDCARE_MATCH_COLUMNS) + 1) +) +POOLED_CHILDCARE_STRENGTH = 10.0 + + +def with_childcare_household_size(children): + """Count the full roster before selecting complete child calendars.""" + result = children.copy() + _ids(result, "source_household_id", unique=False) + age = result.age.to_numpy(dtype=float) + if not np.isfinite(age).all() or (age < 0).any() or (age % 1 != 0).any(): + raise ValueError("Household-size predictors require finite whole-year ages.") + result["childcare_household_size"] = ( + result.age.between(0, 12) + .groupby(result.source_household_id) + .transform("sum") + .clip(upper=3) + ) + return result + + +class PooledScheduleDonors: + """Weighted mixture of nested empirical donor distributions. + + The support statistic uses households as clusters, not child-row counts. + Multiplying all survey weights by a common factor cannot change predictions. + """ + + def __init__(self, children, *, strength=POOLED_CHILDCARE_STRENGTH): + if not np.isfinite(strength) or strength <= 0: + raise ValueError("Pooling strength must be finite and positive.") + self.strength = strength + required = [ + *POOLED_CHILDCARE_MATCH_COLUMNS, + "source_household_id", + "donor_id", + "child_weight", + "attendance_status", + *US_CHILDCARE_ATTENDANCE_COLUMNS, + ] + if not set(required).issubset(children): + raise ValueError("Pooled childcare donors need complete source predictors.") + if children.attendance_status.eq("summary_bridge").any(): + raise ValueError("Experimental pooled donors require original calendars.") + self.pool = children.loc[ + children.attendance_status.eq("complete") & children.age.between(0, 12) + ].copy() + if self.pool.empty or self.pool.reindex(columns=required).isna().any().any(): + raise ValueError("Pooled childcare donor fields must be complete.") + if self.pool.donor_id.duplicated().any(): + raise ValueError("Pooled childcare donor IDs must be unique.") + _ids(self.pool, "source_household_id", unique=False) + _ids(self.pool, "donor_id", unique=True) + predictors = self.pool.reindex(columns=POOLED_CHILDCARE_MATCH_COLUMNS).to_numpy( + dtype=float + ) + if ( + not np.isfinite(predictors).all() + or (predictors % 1 != 0).any() + or not self.pool.childcare_household_size.isin([1, 2, 3]).all() + ): + raise ValueError( + "Pooled childcare predictors must be finite categorical values." + ) + weights = self.pool.child_weight.to_numpy(dtype=float) + if not np.isfinite(weights).all() or (weights <= 0).any(): + raise ValueError("Pooled childcare weights must be finite and positive.") + _validate_attendance(self.pool, complete=True) + _, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS + self.pool = self.pool.sort_values([days, hours, "donor_id"]).reset_index( + drop=True + ) + self.values = np.column_stack( + (self.pool[days] > 0, self.pool[days], self.pool[days] * self.pool[hours]) + ).astype(float) + self.weights = self.pool.child_weight.to_numpy(dtype=float) + self.households = self.pool.source_household_id.to_numpy() + self.groups = [ + self.pool.groupby(list(level), sort=False).indices + for level in POOLED_CHILDCARE_LEVELS + ] + self.cache = {} + + def distribution(self, child, *, exclude_household=None): + """Return care-sorted values, cumulative probabilities and row masses.""" + key = tuple(child.reindex(POOLED_CHILDCARE_MATCH_COLUMNS)) + if not np.isfinite(np.asarray(key, dtype=float)).all(): + raise ValueError("Pooled target matching fields must be complete.") + if exclude_household is None and key in self.cache: + return self.cache[key] + age_indices = np.asarray(self.groups[0].get(key[0], []), dtype=int) + if exclude_household is not None: + age_indices = age_indices[self.households[age_indices] != exclude_household] + if not len(age_indices): + raise ValueError("No exact-age donor household remains after exclusion.") + probabilities = self.weights[age_indices].copy() + probabilities /= probabilities.sum() + for level, groups in zip( + POOLED_CHILDCARE_LEVELS[1:], self.groups[1:], strict=True + ): + indices = np.asarray(groups.get(key[: len(level)], []), dtype=int) + if exclude_household is not None: + indices = indices[self.households[indices] != exclude_household] + if not len(indices): + continue + # Both arrays retain global care order, so local indices embed in + # the exact-age root without a second sort or a donor cross-join. + positions = np.searchsorted(age_indices, indices) + weights = self.weights[indices] + _, cluster = np.unique(self.households[indices], return_inverse=True) + cluster_weights = np.bincount(cluster, weights=weights) + effective = cluster_weights.sum() ** 2 / (cluster_weights @ cluster_weights) + fraction = effective / (effective + self.strength) + probabilities *= 1 - fraction + probabilities[positions] += fraction * weights / weights.sum() + probabilities /= probabilities.sum() + cumulative = probabilities.cumsum() + cumulative[-1] = 1.0 + result = (self.values[age_indices], cumulative, probabilities) + if exclude_household is None: + self.cache[key] = result + return result + + +def fit_pooled_sibling_dependence( + children, *, strength=POOLED_CHILDCARE_STRENGTH, objective="pair_squared_error" +): + """Fit all measured pair cross-products with whole-household donor exclusion.""" + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + _joint_product, + ) + + if objective not in ("pair_squared_error", "population_moments"): + raise ValueError("Unknown pooled dependence objective.") + model = PooledScheduleDonors(children, strength=strength) + records = [] + households = 0 + incomplete_households = 0 + for household_id, roster in children.loc[children.age.between(0, 12)].groupby( + "source_household_id", sort=True + ): + observed = roster.loc[roster.attendance_status.eq("complete")].sort_values( + ["age", "donor_id"] + ) + if len(observed) < 2: + continue + weight = roster.household_weight.to_numpy(dtype=float) + if ( + not np.isfinite(weight).all() + or (weight <= 0).any() + or not (weight == weight[0]).all() + ): + raise ValueError("Pooled dependence requires consistent household weights.") + distributions = [ + model.distribution(row, exclude_household=household_id) + for _, row in observed.iterrows() + ] + means = [np.average(d[0], weights=d[2], axis=0) for d in distributions] + _, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS + actual = np.column_stack( + (observed[days] > 0, observed[days], observed[days] * observed[hours]) + ).astype(float) + pair_count = len(observed) * (len(observed) - 1) // 2 + for i, j in combinations(range(len(observed)), 2): + independent = means[i] * means[j] + shared = _joint_product(distributions[i][:2], distributions[j][:2]) + records.append( + ( + weight[0] / pair_count, + actual[i] * actual[j] - independent, + shared - independent, + (actual[i] ** 2 + actual[j] ** 2) / 2, + ) + ) + households += 1 + incomplete_households += int(len(observed) != len(roster)) + if not records: + raise ValueError("Pooled dependence requires observed sibling pairs.") + weights = np.array([r[0] for r in records]) + residual = np.array([r[1] for r in records]) + direction = np.array([r[2] for r in records]) + scales = np.average(np.array([r[3] for r in records]), weights=weights, axis=0) + active = scales > 0 + residual = residual[:, active] / scales[active] + direction = direction[:, active] / scales[active] + numerator = np.sum(weights[:, None] * residual * direction) + denominator = np.sum(weights[:, None] * direction**2) + mean_residual = np.average(residual, weights=weights, axis=0) + mean_direction = np.average(direction, weights=weights, axis=0) + if objective == "population_moments": + numerator = mean_residual @ mean_direction + denominator = mean_direction @ mean_direction + unconstrained = float(numerator / denominator) if denominator > 0 else 0.0 + return { + "rho": float(np.clip(unconstrained, 0, 1)), + "unconstrained_rho": unconstrained, + "households": households, + "households_with_unresolved_siblings": incomplete_households, + "observed_pairs": len(records), + "outcome_second_moments": scales.tolist(), + "objective": objective, + "normalized_mean_residual": mean_residual.tolist(), + "normalized_mean_shared_increment": mean_direction.tolist(), + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors", + } diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py index 498179b6a..46280adc4 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py @@ -111,6 +111,9 @@ def assess_sibling_schedules( fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS, validation_seed=None, partition="all", + pooled=False, + include_observed_children=False, + pooling_fit_objective="pair_squared_error", ): """Evaluate pair intensity and larger-household totals with disjoint training. @@ -119,6 +122,14 @@ def assess_sibling_schedules( merely because a new split is used. """ children = source.children.loc[source.children.age.between(0, 12)].copy() + if pooled or include_observed_children: + from microcosm.build.us_runtime.nsece_childcare_pooling import ( + PooledScheduleDonors, + fit_pooled_sibling_dependence, + with_childcare_household_size, + ) + + children = with_childcare_household_size(children) if children.attendance_status.eq("summary_bridge").any(): raise ValueError("Sibling validation requires original measured calendars.") levels = (match_columns, *fallback_match_columns) @@ -126,9 +137,11 @@ def assess_sibling_schedules( children, seed=seed, validation_seed=validation_seed, partition=partition ) pairs, large_pairs, larger = [], [], [] + child_records, observed_pairs = [], [] + observed_pair_households = 0 month, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS del month - fold_fits, split_counts, matching_counts = [], [], {} + fold_fits, split_counts, matching_counts, dependence_fits = [], [], {}, [] for training_mask, target_mask in splits: training = children.loc[training_mask] target = children.loc[target_mask] @@ -145,8 +158,15 @@ def assess_sibling_schedules( "household_overlap": 0, } ) - rho = fit_nsece_sibling_dependence(training, match_columns=match_columns)["rho"] + fitted = ( + fit_pooled_sibling_dependence(training, objective=pooling_fit_objective) + if pooled + else fit_nsece_sibling_dependence(training, match_columns=match_columns) + ) + rho = fitted["rho"] + dependence_fits.append(fitted) fold_fits.append(rho) + pooled_model = PooledScheduleDonors(training) if pooled else None train = training.loc[training.attendance_status.eq("complete")].sort_values( [days, hours, "donor_id"] ) @@ -160,7 +180,13 @@ def distribution( cache: dict = cache, groups: list = groups, train: pd.DataFrame = train, + pooled_model=pooled_model, ): + if pooled_model is not None: + matching_counts["partially_pooled"] = ( + matching_counts.get("partially_pooled", 0) + 1 + ) + return pooled_model.distribution(child) key = tuple(child.reindex(match_columns)) if key not in cache: for level, indices in groups: @@ -184,11 +210,15 @@ def distribution( return distribution_values for _, household in target.groupby("source_household_id"): - if ( - len(household) < 2 - or not household.attendance_status.eq("complete").all() + complete_household = bool(household.attendance_status.eq("complete").all()) + if not include_observed_children and ( + len(household) < 2 or not complete_household ): continue + roster_size = len(household) + household = household.loc[household.attendance_status.eq("complete")] + if household.empty: + continue household = household.sort_values(["age", "donor_id"]) w = household.household_weight.to_numpy(dtype=float) if not np.isfinite(w).all() or (w <= 0).any() or not (w == w[0]).all(): @@ -205,6 +235,61 @@ def distribution( household[days] * household[hours], ) ).astype(float) + if include_observed_children: + for i, (_, child) in enumerate(household.iterrows()): + child_records.append( + ( + float(child.child_weight), + min(roster_size, 3), + complete_household, + actual[i], + means[i], + seconds[i], + ) + ) + if len(household) >= 2: + observed_pair_households += 1 + pair_weight = w[0] / (len(household) * (len(household) - 1) / 2) + for i, j in combinations(range(len(household)), 2): + independent_product = means[i] * means[j] + shared_product = _joint_product( + distributions[i][:2], distributions[j][:2] + ) + observed_pairs.append( + ( + pair_weight, + np.array( + [ + actual[i], + actual[j], + actual[i] ** 2, + actual[j] ** 2, + actual[i] * actual[j], + ] + ), + np.array( + [ + means[i], + means[j], + seconds[i], + seconds[j], + independent_product, + ] + ), + np.array( + [ + means[i], + means[j], + seconds[i], + seconds[j], + (1 - rho) * independent_product + + rho * shared_product, + ] + ), + ) + ) + if len(household) < 2 or not complete_household: + continue joint = _joint_product(distributions[0][:2], distributions[1][:2]) independent = means[0] * means[1] observed = np.array( @@ -295,9 +380,199 @@ def distribution( "all_children_attend": float(values[6]), } result["diagnostic_screen"] = sibling_schedule_screen(result) + if pooled: + from microcosm.build.us_runtime.nsece_childcare_pooling import ( + POOLED_CHILDCARE_LEVELS, + POOLED_CHILDCARE_STRENGTH, + ) + + result["model"] = { + "name": "partially_pooled_empirical_schedules", + "levels": POOLED_CHILDCARE_LEVELS, + "strength": POOLED_CHILDCARE_STRENGTH, + "dependence_objective": pooling_fit_objective, + "dependence_fits": dependence_fits, + } + result["match_columns"] = POOLED_CHILDCARE_LEVELS[-1] + result["fallback_match_columns"] = () + if include_observed_children: + result["matching_count_universe"] = ( + "every held-out child with an observed complete calendar" + ) + result["observed_child_validation"] = _observed_child_summary(child_records) + if observed_pairs: + summary = _pair_summary(observed_pairs) + summary["observed_pairs"] = summary.pop("households") + summary["households"] = observed_pair_households + summary["interpretation"] = ( + "all observed pairs including households with unresolved other siblings; household weight divided among observed pairs" + ) + result["all_observed_sibling_pairs"] = summary + else: + result["all_observed_sibling_pairs"] = None return result +def _observed_child_summary(records): + if not records: + raise ValueError("Observed-child validation requires measured calendars.") + outcomes = ("participation", "days", "weekly_hours") + comparisons, checks = [], [] + for label, rows in ( + ("all", records), + *( + (f"household_size_{size}", [r for r in records if r[1] == size]) + for size in (1, 2, 3) + ), + ("complete_household", [r for r in records if r[2]]), + ("unresolved_siblings", [r for r in records if not r[2]]), + ): + if not rows: + comparisons.append({"group": label, "children": 0}) + checks.append( + {"group": label, "passed": False, "reason": "no observed children"} + ) + continue + weights = np.array([r[0] for r in rows]) + actual = np.array([r[3] for r in rows]) + means = np.array([r[4] for r in rows]) + seconds = np.array([r[5] for r in rows]) + observed = np.average(actual, weights=weights, axis=0) + expected = np.average(means, weights=weights, axis=0) + comparisons.append( + { + "group": label, + "children": len(rows), + "child_weight": float(weights.sum()), + "observed": dict(zip(outcomes, observed.tolist(), strict=True)), + "expected": dict(zip(outcomes, expected.tolist(), strict=True)), + "conditional_mean_squared_error": dict( + zip( + outcomes, + np.average( + (actual - means) ** 2, weights=weights, axis=0 + ).tolist(), + strict=True, + ) + ), + "expected_draw_squared_error": dict( + zip( + outcomes, + np.average( + actual**2 - 2 * actual * means + seconds, + weights=weights, + axis=0, + ).tolist(), + strict=True, + ) + ), + } + ) + for index, name in enumerate(outcomes): + relative = index != 0 + gap = float( + abs(expected[index] - observed[index]) + / (abs(observed[index]) if relative and observed[index] != 0 else 1) + ) + defined = bool(not relative or observed[index] != 0) + limit = 0.20 if relative else 0.05 + checks.append( + { + "group": label, + "metric": name, + "relative": relative, + "gap": gap if defined else None, + "limit": limit, + "passed": defined and gap <= limit, + } + ) + return { + "comparisons": comparisons, + "screen": {"passed": all(c["passed"] for c in checks), "checks": checks}, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation", + } + + +def coupling_screen_compatibility(result): + """Check whether changing dependence alone could satisfy BOTH joint screens. + + Independent and coupled arms have identical marginal first/second moments. + Therefore correlation is affine in E[XY], regardless of the chosen copula. + Recover that line from the two arms and intersect the existing correlation + and cross-product screen intervals. Disjoint intervals prove those screens + require a change to marginal distributions, not merely a different rho. + This is conditional on the evaluated marginals, not an impossibility result + for other models or a claim that selected households represent the population. + """ + comparisons = [] + screens = {row["metric"]: row for row in result["diagnostic_screen"]["checks"]} + for population in ("youngest_pairs", "youngest_pairs_in_3plus_households"): + group = result.get(population) + for metric in ("days", "weekly_hours"): + entry = {"population": population, "metric": metric} + comparisons.append(entry) + if not group: + entry.update({"identified": False, "reason": "no evaluated pairs"}) + continue + observed = group["observed"][metric] + independent = group["independent"][metric] + coupled = group["coupled"][metric] + correlations = [ + arm["correlation"] for arm in (observed, independent, coupled) + ] + if any(c is None or not np.isfinite(c) for c in correlations) or np.isclose( + correlations[1], correlations[2] + ): + entry.update( + { + "identified": False, + "reason": "marginal scale not recoverable from the two arms", + } + ) + continue + scale = (coupled["joint_product"] - independent["joint_product"]) / ( + correlations[2] - correlations[1] + ) + if not np.isfinite(scale) or scale <= 0: + entry.update({"identified": False, "reason": "invalid marginal scale"}) + continue + product_of_means = independent["joint_product"] - correlations[1] * scale + # Consume the reported criteria so a screen change cannot leave + # this structural diagnostic silently using a different threshold. + correlation_limit = screens[f"{population}.{metric}.correlation"]["limit"] + product_limit = screens[f"{population}.{metric}.joint_product"]["limit"] + correlation_range = [ + max(-1, correlations[0] - correlation_limit), + min(1, correlations[0] + correlation_limit), + ] + required_product = [ + product_of_means + value * scale for value in correlation_range + ] + allowed_product = [ + (1 - product_limit) * observed["joint_product"], + (1 + product_limit) * observed["joint_product"], + ] + lower, upper = ( + max(required_product[0], allowed_product[0]), + min(required_product[1], allowed_product[1]), + ) + entry.update( + { + "identified": True, + "predicted_product_of_marginal_means": product_of_means, + "predicted_product_of_marginal_standard_deviations": scale, + "joint_product_required_by_correlation_screen": required_product, + "joint_product_allowed_by_product_screen": allowed_product, + "screen_intervals_overlap": lower <= upper, + "overlap_interval": [lower, upper] if lower <= upper else None, + } + ) + return { + "comparisons": comparisons, + "interpretation": "disjoint screen intervals rule out a dependence-only fix for these conditional marginals; overlapping intervals do not establish that a feasible joint distribution exists", + } + + def sibling_schedule_screen(result): """Developmental screens declared before this expanded assessment was run. diff --git a/packages/microcosm-build/tests/test_us_childcare_pooling.py b/packages/microcosm-build/tests/test_us_childcare_pooling.py new file mode 100644 index 000000000..f842a444b --- /dev/null +++ b/packages/microcosm-build/tests/test_us_childcare_pooling.py @@ -0,0 +1,282 @@ +"""Synthetic pooling/evaluation contracts; never load licensed survey records.""" + +import hashlib +import json + +import numpy as np +import pandas as pd +import pytest +from pandas.testing import assert_frame_equal + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.nsece_childcare import NSECEChildcareSource +from microcosm.build.us_runtime.nsece_childcare_pooling import ( + POOLED_CHILDCARE_LEVELS, + POOLED_CHILDCARE_MATCH_COLUMNS, + PooledScheduleDonors, + fit_pooled_sibling_dependence, + with_childcare_household_size, +) +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + assess_sibling_schedules, +) +from microcosm.frame import WeightKind, Weights + +MONTH, DAYS, HOURS = US_CHILDCARE_ATTENDANCE_COLUMNS + + +def _children(households=40): + records = [] + for household in range(households): + for child in range(3): + known = child != 2 or household % 4 != 0 + days = 5.0 if household % 2 else 0.0 + records.append( + { + "donor_id": f"h{household}:c{child}", + "source_household_id": f"h{household}", + "age": 3, + "region": household % 3 + 1, + "parent_work_status": 2, + "income_band": 1, + "attendance_status": "complete" if known else "partial_calendar", + "household_weight": 2.0, + "child_weight": 2.0, + MONTH: (22.0 if days else 0.0) if known else np.nan, + DAYS: days if known else np.nan, + HOURS: (8.0 if days else 0.0) if known else np.nan, + } + ) + return with_childcare_household_size(pd.DataFrame(records)) + + +def test_pooling_levels_have_one_definition_and_retain_age(): + assert POOLED_CHILDCARE_LEVELS[-1] == POOLED_CHILDCARE_MATCH_COLUMNS + assert POOLED_CHILDCARE_LEVELS[0] == ("age",) + for smaller, larger in zip( + POOLED_CHILDCARE_LEVELS, POOLED_CHILDCARE_LEVELS[1:], strict=False + ): + assert smaller == larger[:-1] + + +def test_sparse_cell_blends_distributions_with_household_cluster_support(): + rows = _children().iloc[[0, 1, 3]].copy() + rows["region"] = [1, 1, 2] + # Two zero-care child donors come from one household, so local effective + # support is ONE, not two. The positive root donor has one third of mass. + values, cumulative, probability = PooledScheduleDonors(rows).distribution( + rows.iloc[0] + ) + assert np.average(values[:, 0], weights=probability) == pytest.approx(10 / 33) + assert probability.sum() == pytest.approx(1) + assert cumulative[-1] == 1 + assert (probability > 0).all() + + +def test_entire_household_is_excluded_even_after_unexcluded_cache_lookup(): + rows = _children().iloc[[0, 1, 3]].copy() + model = PooledScheduleDonors(rows) + model.distribution(rows.iloc[0]) + values, _, weights = model.distribution(rows.iloc[0], exclude_household="h0") + assert values.tolist() == [[1, 5, 40]] + assert weights.tolist() == [1] + rows.loc[rows.source_household_id.eq("h1"), "age"] = 4 + with pytest.raises(ValueError, match="exact-age"): + PooledScheduleDonors(rows).distribution(rows.iloc[0], exclude_household="h0") + + +def test_pooling_is_row_order_and_weight_scale_invariant_and_keeps_joint_schedules(): + children = _children() + before = children.copy(deep=True) + target = children.iloc[0].copy() + target["region"] = 9 # No exact region; use the broader empirical mixture. + original = PooledScheduleDonors(children).distribution(target) + changed = children.iloc[::-1].copy() + changed["child_weight"] *= 1000 + reordered = PooledScheduleDonors(changed).distribution(target) + for a, b in zip(original, reordered, strict=True): + np.testing.assert_allclose(a, b) + assert set(map(tuple, original[0])) == {(0, 0, 0), (1, 5, 40)} + assert_frame_equal(children, before) + + +def test_roster_count_includes_unknown_attendance_but_not_older_children(): + children = _children(2).drop(columns="childcare_household_size") + children.loc[5, "age"] = 13 + counted = with_childcare_household_size(children) + assert counted.childcare_household_size.tolist() == [3, 3, 3, 2, 2, 2] + assert children.loc[2, "attendance_status"] == "partial_calendar" + assert "childcare_household_size" not in children + + +def test_dependence_uses_observed_pairs_in_incomplete_households(monkeypatch): + original = PooledScheduleDonors.distribution + exclusions = [] + + def checked(self, child, *, exclude_household=None): + assert exclude_household == child.source_household_id + exclusions.append(exclude_household) + return original(self, child, exclude_household=exclude_household) + + monkeypatch.setattr(PooledScheduleDonors, "distribution", checked) + fit = fit_pooled_sibling_dependence(_children()) + assert fit["households"] == 40 + assert fit["households_with_unresolved_siblings"] == 10 + assert fit["observed_pairs"] == 100 + assert len(exclusions) == 110 + assert 0 <= fit["rho"] <= 1 + + +def test_outer_folds_refit_without_heldout_households_and_score_all_observed_children( + monkeypatch, +): + import microcosm.build.us_runtime.nsece_childcare_pooling as module + + children = _children() + source = NSECEChildcareSource( + children, Weights(children.child_weight.to_numpy(), WeightKind.DESIGN), {} + ) + original = module.fit_pooled_sibling_dependence + fold = 0 + + def checked(training, **kwargs): + nonlocal fold + heldout = { + household + for household in children.source_household_id.unique() + if int.from_bytes( + hashlib.sha256(f"271828:{household}".encode()).digest()[:8], "big" + ) + % 5 + == fold + } + assert set(training.source_household_id).isdisjoint(heldout) + fold += 1 + return original(training, **kwargs) + + monkeypatch.setattr(module, "fit_pooled_sibling_dependence", checked) + report = assess_sibling_schedules( + source, pooled=True, include_observed_children=True + ) + # An observed-zero subgroup has undefined relative-error screens. These + # must remain failed, JSON-serializable results, not numpy boolean objects. + json.dumps(report, allow_nan=False) + assert fold == 5 + groups = { + row["group"]: row for row in report["observed_child_validation"]["comparisons"] + } + assert groups["all"]["children"] == 110 + assert groups["unresolved_siblings"]["children"] == 20 + assert report["larger_households"]["households"] == 30 + assert report["all_observed_sibling_pairs"]["households"] == 40 + assert report["all_observed_sibling_pairs"]["observed_pairs"] == 100 + assert all(part["household_overlap"] == 0 for part in report["splits"]) + + +@pytest.mark.parametrize("strength", [0, -1, np.inf, np.nan]) +def test_invalid_pooling_strength_is_rejected(strength): + with pytest.raises(ValueError, match="strength"): + PooledScheduleDonors(_children(), strength=strength) + + +def test_reconstructed_calendars_never_enter_the_experimental_truth_pool(): + children = _children() + children.loc[0, "attendance_status"] = "summary_bridge" + with pytest.raises(ValueError, match="original calendars"): + PooledScheduleDonors(children) + + +def test_population_moment_objective_matches_its_analytic_coefficient(): + children = _children() + fitted = fit_pooled_sibling_dependence(children, objective="population_moments") + residual = np.array(fitted["normalized_mean_residual"]) + increment = np.array(fitted["normalized_mean_shared_increment"]) + optimum = residual @ increment / (increment @ increment) + assert fitted["unconstrained_rho"] == pytest.approx(optimum) + assert fitted["rho"] == pytest.approx(np.clip(optimum, 0, 1)) + # A population coefficient is one joint scalar, not a different rho picked + # for each outcome to force all the evaluation moments to match. + for alternative in ( + 0, + 1, + max(0, fitted["rho"] - 0.05), + min(1, fitted["rho"] + 0.05), + ): + chosen_loss = np.sum((residual - fitted["rho"] * increment) ** 2) + other_loss = np.sum((residual - alternative * increment) ** 2) + assert chosen_loss <= other_loss + 1e-12 + + +def test_dependence_objective_does_not_change_child_donor_distributions(): + children = _children() + before = children.copy(deep=True) + model = PooledScheduleDonors(children) + values = model.distribution(children.iloc[0]) + fit_pooled_sibling_dependence(children, objective="population_moments") + fit_pooled_sibling_dependence(children, objective="pair_squared_error") + for original, after in zip( + values, model.distribution(children.iloc[0]), strict=True + ): + np.testing.assert_array_equal(original, after) + assert_frame_equal(children, before) + + +def test_unknown_dependence_objective_is_rejected(): + with pytest.raises(ValueError, match="objective"): + fit_pooled_sibling_dependence(_children(), objective="unknown") + + +@pytest.mark.parametrize("observed_product,overlap", [(1.0, False), (3.0, True)]) +def test_dependence_screen_compatibility_uses_fixed_marginal_identity( + observed_product, overlap +): + from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + coupling_screen_compatibility, + sibling_schedule_screen, + ) + + # Fixed E[X]E[Y]=1 and SD[X]SD[Y]=4: corr=(E[XY]-1)/4. + group = { + "observed": { + m: {"joint_product": observed_product, "correlation": 0.5} + for m in ("days", "weekly_hours") + }, + "independent": { + m: {"joint_product": 1.0, "correlation": 0.0} + for m in ("days", "weekly_hours") + }, + "coupled": { + m: {"joint_product": 3.0, "correlation": 0.5} + for m in ("days", "weekly_hours") + }, + } + evaluated = {"youngest_pairs": group, "larger_households": {}} + evaluated["diagnostic_screen"] = sibling_schedule_screen(evaluated) + report = coupling_screen_compatibility(evaluated) + result = report["comparisons"][0] + assert result["predicted_product_of_marginal_standard_deviations"] == 4 + assert result["joint_product_required_by_correlation_screen"] == pytest.approx( + [2.6, 3.4] + ) + assert result["screen_intervals_overlap"] == overlap + assert not report["comparisons"][-1]["identified"] + + +@pytest.mark.parametrize("age", [np.nan, np.inf, -1, 3.5]) +def test_unknown_or_invalid_roster_age_cannot_reduce_household_size(age): + children = _children() + children["age"] = children.age.astype(float) + children.loc[0, "age"] = age + with pytest.raises(ValueError, match="whole-year ages"): + with_childcare_household_size(children) + + +@pytest.mark.parametrize("field", ["region", "child_weight"]) +def test_nonfinite_donor_inputs_are_rejected(field): + children = _children() + children[field] = children[field].astype(float) + children.loc[0, field] = np.inf + with pytest.raises(ValueError, match="finite"): + PooledScheduleDonors(children) diff --git a/packages/microcosm-build/tests/test_us_spine_blindness.py b/packages/microcosm-build/tests/test_us_spine_blindness.py index 234be6951..1cab00db0 100644 --- a/packages/microcosm-build/tests/test_us_spine_blindness.py +++ b/packages/microcosm-build/tests/test_us_spine_blindness.py @@ -263,6 +263,7 @@ "nsece_childcare_assessment.py", # Source selection and household validation. "nsece_childcare_sibling_validation.py", # Whole-household schedule diagnostics. "nsece_childcare_dependence.py", # Household dependence estimation. + "nsece_childcare_pooling.py", # Experimental donor pooling; no source-spine routing. "nsece_childcare_bridge.py", # Source measurement completion; no spine routing. "operator_boundary.py", # Raw-stage validator; no population treatment. "org_wages.py", diff --git a/tools/validate_us_childcare_pooling.py b/tools/validate_us_childcare_pooling.py new file mode 100644 index 000000000..4a71beaa5 --- /dev/null +++ b/tools/validate_us_childcare_pooling.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Compare fixed partially pooled matching with the existing attendance model.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + +from microcosm.build.us_runtime import ( + nsece_childcare_pooling, + nsece_childcare_sibling_validation, +) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + attendance_recipe_identity, +) +from microcosm.build.us_runtime.nsece_childcare import load_nsece_childcare +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + assess_sibling_schedules, + coupling_screen_compatibility, +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + parser.add_argument( + "--compare-moment-fit", + action="store_true", + help="Include the separately planned exploratory population-moment objective", + ) + args = parser.parse_args() + if args.report.exists(): + parser.error("Report path must be new") + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + plan = ( + Path(__file__).resolve().parents[1] + / "experiments/us-childcare-attendance/pooled-matching-plan.txt" + ) + report = { + "source": source.source_receipt, + "plan_sha256": hashlib.sha256(plan.read_bytes()).hexdigest(), + "recipe": attendance_recipe_identity(), + "diagnostic_code_sha256": { + path.name: hashlib.sha256(path.read_bytes()).hexdigest() + for path in ( + Path(__file__), + Path(nsece_childcare_pooling.__file__), + Path(nsece_childcare_sibling_validation.__file__), + ) + }, + "production_ready": False, + "production_recipe_changed": False, + "interpretation": "five household-separated folds on previously inspected development data; no untouched evaluation claim; calendar selection remains unidentified", + } + arms = [ + ("legacy", False, "pair_squared_error"), + ("pooled", True, "pair_squared_error"), + ] + if args.compare_moment_fit: + extra_plan = plan.with_name("pooled-moments-plan.txt") + report["moment_plan_sha256"] = hashlib.sha256( + extra_plan.read_bytes() + ).hexdigest() + arms.append(("pooled_moments", True, "population_moments")) + for name, pooled, objective in arms: + report[name] = assess_sibling_schedules( + source, + pooled=pooled, + include_observed_children=True, + pooling_fit_objective=objective, + ) + report[name]["dependence_only_screen_compatibility"] = ( + coupling_screen_compatibility(report[name]) + ) + print(name, json.dumps(report[name]["larger_households"]), flush=True) + print( + "joint failures", + sum(not c["passed"] for c in report[name]["diagnostic_screen"]["checks"]), + flush=True, + ) + print( + "child failures", + sum( + not c["passed"] + for c in report[name]["observed_child_validation"]["screen"]["checks"] + ), + flush=True, + ) + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + + +if __name__ == "__main__": + main() From 28b83fccd874a211aa9188187d4dadf556e39055 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Wed, 16 Sep 2026 19:28:14 -0400 Subject: [PATCH 09/16] Audit NSECE calendar uncertainty and revalidate attendance on US 2.2.1 --- docs/us-childcare-attendance.md | 28 +- experiments/us-childcare-attendance/README.md | 183 +- .../calendar-review-2.2.1-population.json | 1578 ++++++++ .../calendar-review-2.2.1-sensitivity.json | 3220 +++++++++++++++++ .../calendar-review-2.2.1-source-stage.json | 1994 ++++++++++ .../calendar-review-2.2.1-verification.json | 39 + .../calendar-selection-plan.txt | 46 + .../calendar-selection-validation.json | 1896 ++++++++++ .../qrf-calendar-plan.txt | 42 + .../qrf-calendar-validation.json | 417 +++ .../build/us_runtime/nsece_childcare.py | 25 + .../us_runtime/nsece_childcare_pooling.py | 62 +- .../build/us_runtime/nsece_childcare_qrf.py | 150 + .../nsece_childcare_sibling_validation.py | 20 +- .../tests/test_us_childcare_pooling.py | 26 + .../tests/test_us_childcare_qrf.py | 115 + .../tests/test_us_nsece_childcare.py | 50 + .../tests/test_us_spine_blindness.py | 1 + ...alidate_us_childcare_calendar_selection.py | 133 + tools/validate_us_childcare_qrf.py | 136 + tools/verify_us_childcare_candidate.py | 142 + 21 files changed, 10273 insertions(+), 30 deletions(-) create mode 100644 experiments/us-childcare-attendance/calendar-review-2.2.1-population.json create mode 100644 experiments/us-childcare-attendance/calendar-review-2.2.1-sensitivity.json create mode 100644 experiments/us-childcare-attendance/calendar-review-2.2.1-source-stage.json create mode 100644 experiments/us-childcare-attendance/calendar-review-2.2.1-verification.json create mode 100644 experiments/us-childcare-attendance/calendar-selection-plan.txt create mode 100644 experiments/us-childcare-attendance/calendar-selection-validation.json create mode 100644 experiments/us-childcare-attendance/qrf-calendar-plan.txt create mode 100644 experiments/us-childcare-attendance/qrf-calendar-validation.json create mode 100644 packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py create mode 100644 packages/microcosm-build/tests/test_us_childcare_qrf.py create mode 100644 tools/validate_us_childcare_calendar_selection.py create mode 100644 tools/validate_us_childcare_qrf.py create mode 100644 tools/verify_us_childcare_candidate.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index d762728bb..70a185c1e 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -15,12 +15,13 @@ build inputs; CI does not download or redistribute survey records. This PR provides build integration and a local population candidate under review; it does not publish a replacement population or certify national CCDF spending. -The September 16 main merge upgrades the branch to PolicyEngine-US 2.2.1. -Existing population candidates and state-benefit reports were generated with -1.819.0 and remain historical evidence. Their runtime-bound attendance receipts -cannot be reused under 2.2.1: a new candidate must be rebuilt from the original -parent and revalidated before use. The survey-model experiments do not provide -that population validation. +The September 16 candidate was rebuilt from the pinned parent under +PolicyEngine-US 2.2.1 and Core 3.32.5. Both native loaders verify the new receipt; +all original values and weights are preserved. The new all-state comparison +reduces all-zero results from 31 jurisdictions to two (MD and NV). The +[aggregate experiment](../experiments/us-childcare-attendance/README.md) records +the current population, sensitivity and model diagnostics. Older 1.819.0 reports +remain historical; their receipts are not reused under the new runtime. ## Source and mapping @@ -54,6 +55,14 @@ at age six or older. Ambiguous codes remain unknown. A complete parental, self-care, or school-only calendar is a measured zero donor. Missing calendars never become observed zeros. +Diagnostic `calendar_ece_hours_lower/upper` and `calendar_ece_days_lower/upper` +retain the definite and possible care in incomplete calendars. For partial +calendars, code 0 is unresolved because unreported time can be encoded as assumed +parental care (User Guide HH-334). Wholly missing calendars have uninformative +bounds. These fields do not populate attendance inputs. Regular-instrument +summary hours derive from the same calendar and are not independent evidence +for completing its missing blocks. + Attendance uses the union of classified ECE blocks. Days count days with any ECE; hours per day equal weekly ECE hours divided by days. Monthly days use `floor(days_per_week * 52 / 12 + 0.5)` (five days becomes 22). This represents a @@ -111,6 +120,13 @@ See the [plans and full comparison](../experiments/us-childcare-attendance/READM for source-selection limits and reproducible commands. All inspected survey partitions now count as development evidence. +Two further diagnostics add roster composition or use `microcosm.fit`'s canonical +weighted QRF with common household predictors. Composition reduces household +screen failures to one but worsens the unresolved-sibling hours error to −42.8%. +QRF closely matches overall means while still underpredicting that subgroup's +hours by 36.9%, so it was not advanced to joint/production integration. Both +remain experimental; neither relaxes the selection or transport assumptions. + ## ASEC target harmonization `harmonize_asec_childcare_predictors` resolves `PEPAR1` and `PEPAR2` against diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index 5b8a70dd1..56693b86c 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,4 +1,174 @@ -# NSECE attendance pooled-model investigation — 2026-09-16 +# NSECE attendance: calendar selection and current-runtime validation — 2026-09-16 + +**PR #916 remains draft.** The population has now been rebuilt from the pinned +parent under **PolicyEngine-US 2.2.1 / Core 3.32.5**. Two additional marginal +models were tested, but neither resolves the observed-child selection problem; +neither replaces the existing production-stage matcher. No population is +published, and all reports retain `production_ready: false`. + +## Calendar information that can actually be recovered + +The adapter now preserves definite/possible ECE hours and days separately from +completed attendance. The NSECE Household User Guide (HH-334) says unreported +time can be encoded as assumed parental care and derived summaries are not +adjusted for missing-calendar status. Partial-calendar zeros therefore cannot +be treated as observed nonattendance. Summary hours from the regular instrument +derive from the same calendar and cannot independently recover its missing time. +Summer/fall regular hours remain a separate measurement, as before. + +The new fields are diagnostics, not imputed attendance inputs. Unknown schedules +stay unknown. Bounds use known ECE blocks as the lower endpoint and all unresolved +blocks as possible ECE at the upper endpoint. A wholly missing calendar has the +uninformative interval 0–168 hours and 0–7 days. Complete calendars have equal +endpoints. These are identification ranges conditional on the published calendar +classifications, not confidence intervals, plausible point estimates, or benefit +bounds. + +| Regular-questionnaire group | Children | Weighted mean weekly-hours interval | +| --- | ---: | ---: | +| Complete calendars | 7,460 | 14.24–14.24 | +| Ambiguous classifications | 882 | 9.86–32.60 | +| Partial calendars | 174 | 13.19–149.35 | +| All regular-questionnaire children | 8,516 | 13.75–19.28 | + +These broad ranges explain why simply filling the excluded calendars is not a +measured solution. Only 11.4% of the ambiguous-calendar design weight has an +hours interval no wider than one hour. The 149-hour partial-calendar upper bound +means the questionnaire supplies little information; it is not a proposed +attendance schedule. See the [bounds and composition report](calendar-selection-validation.json) +and [plan recorded before that comparison](calendar-selection-plan.txt). + +## Additional model comparisons + +The composition model adds the full roster's under-6 and school-age counts, +youngest age band, and presence of a younger sibling to the pooled matcher. +Missing calendars still count toward these features. The separately declared +QRF experiment uses `microcosm.fit`'s canonical weighted-bootstrap conditional +model, adding measured household income, resident count, and resident-parent +count. An ordinal schedule code is decoded to an observed joint day/hour pair +at the child's exact age. No source response indicator, care outcome, or +incompatible annual/weekly care-expense measure is used as a predictor. + +Every model excludes the evaluation household from training. All 7,460 measured +children are evaluated in the same five folds, including 761 children whose +siblings have unresolved calendars. The QRF uses a fixed 128-point integration +grid; its failed marginal screen did not justify proceeding to dependence fitting +or production integration. All these survey partitions are development data. + +| Model | Failed household screens / 15 | Failed child screens / 18 | Hours error for observed children with unresolved siblings | +| --- | ---: | ---: | ---: | +| Previous pooled population-moment model | 2 | 3 | −40.7% | +| Composition-aware pooled model | 1 | 3 | −42.8% | +| Canonical QRF marginal diagnostic | Not evaluated | 3 | −36.9% | + +The composition model improves the joint-screen count but worsens the important +subgroup. QRF reproduces overall mean weekly hours (14.285 predicted versus +14.240 observed), yet predicts only 15.047 versus 23.830 for the unresolved-sibling +group. Its conditional-mean hours MSE is 495.370 versus 482.798 for the previous +pooled model; subgroup MSE also worsens. Better overall means are insufficient +to adopt either model. These subgroup discrepancies indicate a selection concern; +they do not identify the missing children's outcomes or prove a causal +missingness effect. + +- [QRF plan recorded before its results](qrf-calendar-plan.txt) +- [QRF model, fold counts and marginal diagnostics](qrf-calendar-validation.json) + +## Population rebuilt under PolicyEngine-US 2.2.1 + +The current candidate contains **166,321 people, 57,240 households and 31,889 +children ages 0–12**, with every under-13 attendance input resolved. It uses the +existing default attendance recipe; none of the experimental models above is +used. The current source adapter's bounds do not change the donor attendance. + +The attendance-only counterfactual now reduces all-zero results from **31 to 2 +jurisdictions (MD and NV)**. Annual potential modeled benefits rise from +**$2.254 billion to $5.998 billion**. These use 2026 policies on the parent's +fixed ages/incomes, without aging or uprating; they are not calibrated spending +or caseload estimates. Positive benefits alone do not validate attendance. + +- [Current source-stage report](calendar-review-2.2.1-source-stage.json) +- [Current population and all-state comparison](calendar-review-2.2.1-population.json) +- [Current native-loader verification](calendar-review-2.2.1-verification.json) +- [Current noncalendar sensitivity](calendar-review-2.2.1-sensitivity.json) + +Both native loaders verify the saved attendance binding against the current +recipe and runtime. Verification compares every attendance value, every original +population column and all weights with the checkpoint/parent. Only the pandas +string-storage backend is normalized for the comparison; values, missingness and +other dtypes must match exactly. The source-stage exporter also verifies period +preservation. No stale-receipt override is used. + +The noncalendar stress test preserves measured regular weekly hours and varies +only reconstructed schedules, then repeats the transfer with the same seed and +weights. Its baseline and candidate results match the population report exactly +in every state, and both reports bind the same current recipe. + +| Noncalendar assumption | Annual potential benefits | Change from candidate | +| --- | ---: | ---: | +| No modeled irregular hours | $5.792 billion | −3.43% | +| One fewer modeled day | $5.934 billion | −1.06% | +| One more modeled day | $6.010 billion | +0.20% | + +All three national changes remain below the provisional 10% screen. Six state +comparisons exceed 20%: TN (−36.0%, no irregular hours), IA (+60.0%), KS (−49.4%), +MS (−69.6%) and OK (−40.0%) with one fewer day, and AR (+43.9%) with one more +day. The OK flag represents only about $2 on a roughly $5 baseline; the report +includes absolute changes so tiny denominators are visible. Days and daily-hour +rates interact, so benefit changes need not follow the direction of the day +change. These are assumption scenarios, not confidence intervals, and they do +not resolve the underlying measurement gap. + +## Reproduction and remaining decision + +Run the source experiments on the two licensed files with fresh report paths: + +```bash +uv run python tools/validate_us_childcare_calendar_selection.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --report /local/calendar-selection.json +uv run python tools/validate_us_childcare_qrf.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --report /local/qrf-calendar.json +``` + +Rebuild with `tools/prepare_us_childcare_attendance.py --production-stage +--extended-assessment --inherit-outside-domain-baseline`, the original pinned +parent, both source files and the pinned ASEC cache. Then run +`tools/validate_us_childcare_population.py`, +`tools/validate_us_childcare_sensitivity.py`, and +`tools/verify_us_childcare_candidate.py` on those explicit local artifacts. +Each tool's `--help` lists the required file/hash arguments; no report is +overwritten. Only aggregate reports are committed. + +**Decision:** retain the draft and fail-closed release checks. The current-runtime +rebuild closes the stale-runtime evidence gap. Calendar nonresponse and transport +of unmeasured days/irregular care remain substantive model limitations. A defensible +next model needs independent measured attendance information or a declared +missingness model evaluated across plausible assumptions, followed by fresh +validation. Reusing calendar-derived summaries as truth, conditioning target +predictions on a source-only response flag, or tuning against these already +inspected screens would not resolve those limitations. Provider/activity inputs +and care outside ages 0–12 remain separate gaps. + +## Code validation for this update + +All **1,048 distinct local regression checks** pass in their latest applicable +run: 547 source, attendance, pooling, builder, coverage, export and serializer +checks, plus the final 501-test QRF/architecture rerun. The initial architecture +failure required making the QRF predictor selection explicit; the guard remains +unchanged. The final real-source QRF replay reproduces every earlier metric +exactly and records the final code hash. + +Repository-wide ruff lint, changed-file formatting, tracked CI inventory and +built-wheel source-byte checks pass. A repository-wide formatting check reports +77 pre-existing files; none is part of this update, and they were not reformatted. +The new flat `test_us_childcare_qrf.py` enters the US CI inventory; CI uses +synthetic records only. Native artifact verification runs separately on the +licensed local inputs. GitHub CI has not been monitored. + +## Earlier pooled-model investigation — 2026-09-16 **PR #916 remains draft.** A partially pooled donor model improves conditional child predictions and larger-family means. An exploratory population-moment @@ -6,12 +176,11 @@ dependence fit passes 13 of the original 15 household screens, but still fails two hours checks and all three checks for observed children with unresolved siblings. Neither experimental model has replaced the production-stage recipe. -**Runtime update after merging main:** the branch now uses PolicyEngine-US -2.2.1 and Core 3.32.5. Population artifacts, native-loader receipts and benefit -estimates below were produced with PolicyEngine-US 1.819.0 and Core 3.31.0. -They remain historical evidence, not validation of a population under the new -runtime. Runtime-bound attendance receipts require rebuilding from the original -parent before a new candidate can be used; the merge does not bypass that check. +**Historical runtime note:** the earlier population artifacts, native-loader +receipts and benefit estimates below used PolicyEngine-US 1.819.0 and Core +3.31.0. The current-runtime rebuild above supersedes their population evidence. +The merge itself did not bypass the requirement to rebuild from the original +parent when the receipt's runtime changed. The complete three-model survey comparison was rerun with 2.2.1: every model result and diagnostic code hash matches the pre-upgrade run exactly. The linked pooled report records the new runtime; this survey replay is separate from diff --git a/experiments/us-childcare-attendance/calendar-review-2.2.1-population.json b/experiments/us-childcare-attendance/calendar-review-2.2.1-population.json new file mode 100644 index 000000000..7a9c95fa3 --- /dev/null +++ b/experiments/us-childcare-attendance/calendar-review-2.2.1-population.json @@ -0,0 +1,1578 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "candidate_checkpoint_sha256": "31e4a80cc896aeb769ea034a60e8be68c81bed35a52ff52e27ca62d8cb46f40d", + "validation_code_sha256": "ee3ca65dcd2ef4492924edce54b6bdd03b8b4ed882c65684fc57a1622c341dfe", + "engine_version": "2.2.1", + "policy_year": 2026, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "people": 166321, + "households": 57240, + "under13_children": 31889, + "outside_source_domain_people": 134432, + "outside_source_domain_age13_17_with_disability": 557, + "outside_domain_baseline_receipt": { + "engine_version": "2.2.1", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "unresolved_people_retaining_baseline": 0, + "unresolved_age13_17_with_disability": 0, + "weighted_under13_attendance_share": 0.48474940604537897, + "weighted_under13_days_per_week": 1.9587696980712788, + "weighted_under13_hours_per_week": 14.080797907203973, + "candidate_receipt": { + "nsece_childcare_attendance": { + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "calendar_bounds": { + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "missing_calendar": "uninformative 0--168 hours and 0--7 days", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568" + }, + "candidate_only": true, + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "noncalendar_bridge": { + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", + "completed_children": 3046, + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "observed": "regular weekly hours from May/fall questionnaire", + "seed": 915, + "unsupported_children": 0 + }, + "seed": 915, + "sibling_dependence": 0.7413149380294699, + "source_year": 2024, + "study": "ICPSR39466.v1" + }, + "childcare_predictor_harmonization": { + "geography": "Census region from target state_fips", + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars", + "income_source_receipts": [ + { + "income_year": 2022, + "matched_people": 54464, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "source_rows": 146133 + }, + { + "income_year": 2023, + "matched_people": 54654, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "source_rows": 144265 + }, + { + "income_year": 2024, + "matched_people": 57203, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "source_rows": 142125 + } + ], + "parent_universe": "parents of any child age 0 through 12 in household", + "source": "ASEC resident parent line pointers", + "work_measure": "hours_worked_last_week > 0" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "childcare_attendance_stage": { + "modeled_age_domain": [ + 0, + 12 + ], + "operations": [ + { + "age_max": 12, + "age_min": 0, + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance" + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "nearest_donors": 10, + "operation": "regular_hours_schedule_bridge" + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "outside_domain_policy": "inherit_engine_baseline", + "seed": 915, + "sibling_dependence": { + "estimation": "youngest pair in fully observed households; household design weights", + "households": 1941, + "independent_both_in_care": 0.2585427028841666, + "observed_both_in_care": 0.32858869865910467, + "rho": 0.7413149380294699, + "shared_both_in_care": 0.3530315526045447, + "unconstrained_rho": 0.7413149380294699 + }, + "stage": "nsece_childcare_attendance" + }, + "childcare_attendance_binding": { + "schema_version": 1, + "binding_sha256": "12d583e1e77f9c53ba57b80c5dc16680e073dcd4ca5ba05e4df1c276b0872e99", + "execution_sha256": "c63f973a39fbe658f31b4408d97363fb5e9a684ad4b897c01b0f8b22e85746a7", + "source_people": 166321, + "retained_people": 166321, + "execution": { + "context": { + "childcare_attendance_stage": { + "modeled_age_domain": [ + 0, + 12 + ], + "operations": [ + { + "age_max": 12, + "age_min": 0, + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance" + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "nearest_donors": 10, + "operation": "regular_hours_schedule_bridge" + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "outside_domain_policy": "inherit_engine_baseline", + "seed": 915, + "sibling_dependence": { + "estimation": "youngest pair in fully observed households; household design weights", + "households": 1941, + "independent_both_in_care": 0.2585427028841666, + "observed_both_in_care": 0.32858869865910467, + "rho": 0.7413149380294699, + "shared_both_in_care": 0.3530315526045447, + "unconstrained_rho": 0.7413149380294699 + }, + "stage": "nsece_childcare_attendance" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "childcare_predictor_harmonization": { + "geography": "Census region from target state_fips", + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars", + "income_source_receipts": [ + { + "income_year": 2022, + "matched_people": 54464, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "source_rows": 146133 + }, + { + "income_year": 2023, + "matched_people": 54654, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "source_rows": 144265 + }, + { + "income_year": 2024, + "matched_people": 57203, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "source_rows": 142125 + } + ], + "parent_universe": "parents of any child age 0 through 12 in household", + "source": "ASEC resident parent line pointers", + "work_measure": "hours_worked_last_week > 0" + }, + "nsece_childcare_attendance": { + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "calendar_bounds": { + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "missing_calendar": "uninformative 0--168 hours and 0--7 days", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568" + }, + "candidate_only": true, + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "noncalendar_bridge": { + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", + "completed_children": 3046, + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "observed": "regular weekly hours from May/fall questionnaire", + "seed": 915, + "unsupported_children": 0 + }, + "seed": 915, + "sibling_dependence": 0.7413149380294699, + "source_year": 2024, + "study": "ICPSR39466.v1" + } + }, + "recipe": { + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09" + }, + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + } + } + } + } + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159527.697095435 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 336693781.677099 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 249203866.21813098 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.243193686 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + } + } + ], + "production_ready": false, + "interpretation": "attendance-only counterfactual; provider, activity, expenses and take-up inputs remain as in parent", + "matching_levels": { + "age,region,parent_work_status,income_band": 31152, + "age,parent_work_status,income_band": 576, + "age,parent_work_status": 161 + }, + "population_attendance_by_group": [ + { + "grouping": "age", + "group": "0.0", + "children": 1689, + "weighted_children": 2750700.861434371, + "attendance_share": 0.3982910388950054, + "days_per_week": 1.4637084569333976, + "hours_per_week": 12.47778129720075 + }, + { + "grouping": "age", + "group": "1.0", + "children": 2157, + "weighted_children": 3858818.186826853, + "attendance_share": 0.5828056228035874, + "days_per_week": 2.420690450933124, + "hours_per_week": 20.799764139530804 + }, + { + "grouping": "age", + "group": "2.0", + "children": 2364, + "weighted_children": 3754041.8444214575, + "attendance_share": 0.6245468848119025, + "days_per_week": 2.622672601643104, + "hours_per_week": 21.59076854224297 + }, + { + "grouping": "age", + "group": "3.0", + "children": 2385, + "weighted_children": 3880377.4871301977, + "attendance_share": 0.6440103162467938, + "days_per_week": 2.9239564940965894, + "hours_per_week": 21.837287866122146 + }, + { + "grouping": "age", + "group": "4.0", + "children": 2482, + "weighted_children": 4358831.600945832, + "attendance_share": 0.7298361168227924, + "days_per_week": 3.346378469862117, + "hours_per_week": 26.819455757918746 + }, + { + "grouping": "age", + "group": "5.0", + "children": 2391, + "weighted_children": 4095170.7360430276, + "attendance_share": 0.5643585836076381, + "days_per_week": 2.549062633648557, + "hours_per_week": 18.492995302361113 + }, + { + "grouping": "age", + "group": "6.0", + "children": 2562, + "weighted_children": 4029009.778184548, + "attendance_share": 0.4495509704841995, + "days_per_week": 1.6790815724045138, + "hours_per_week": 10.176170030770823 + }, + { + "grouping": "age", + "group": "7.0", + "children": 2612, + "weighted_children": 3768786.8623211696, + "attendance_share": 0.348461328136747, + "days_per_week": 1.4555085622939352, + "hours_per_week": 9.205290314751595 + }, + { + "grouping": "age", + "group": "8.0", + "children": 2689, + "weighted_children": 4419397.563846819, + "attendance_share": 0.41984770408640326, + "days_per_week": 1.582158698623752, + "hours_per_week": 9.889788909271815 + }, + { + "grouping": "age", + "group": "9.0", + "children": 2552, + "weighted_children": 3888389.065045069, + "attendance_share": 0.37587948153623024, + "days_per_week": 1.3231923573550826, + "hours_per_week": 8.472614481687524 + }, + { + "grouping": "age", + "group": "10.0", + "children": 2662, + "weighted_children": 4369978.335878741, + "attendance_share": 0.43521505778260355, + "days_per_week": 1.5068808517461778, + "hours_per_week": 8.205007778013812 + }, + { + "grouping": "age", + "group": "11.0", + "children": 2795, + "weighted_children": 4689133.259098214, + "attendance_share": 0.4034636828760653, + "days_per_week": 1.4253271874750526, + "hours_per_week": 8.173318571609755 + }, + { + "grouping": "age", + "group": "12.0", + "children": 2549, + "weighted_children": 4093051.641607744, + "attendance_share": 0.31020269689115165, + "days_per_week": 1.1064542406772742, + "hours_per_week": 7.828746644810994 + }, + { + "grouping": "region", + "group": "1", + "children": 5553, + "weighted_children": 7976838.3562431075, + "attendance_share": 0.5123696490920082, + "days_per_week": 2.208084285948932, + "hours_per_week": 15.123524314240859 + }, + { + "grouping": "region", + "group": "2", + "children": 7790, + "weighted_children": 10772583.599183697, + "attendance_share": 0.5153609964315923, + "days_per_week": 1.9523623395589054, + "hours_per_week": 14.70850903160164 + }, + { + "grouping": "region", + "group": "3", + "children": 10750, + "weighted_children": 21007309.669184513, + "attendance_share": 0.44174742437852166, + "days_per_week": 1.7895644362796808, + "hours_per_week": 13.033216088883725 + }, + { + "grouping": "region", + "group": "4", + "children": 7796, + "weighted_children": 12198955.598172724, + "attendance_share": 0.5137082633621989, + "days_per_week": 2.0927835451441785, + "hours_per_week": 14.648645297341831 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "children": 684, + "weighted_children": 1044954.9507498589, + "attendance_share": 0.5422109593087965, + "days_per_week": 2.2811995403367784, + "hours_per_week": 25.482061788407137 + }, + { + "grouping": "parent_work_status", + "group": "0", + "children": 2876, + "weighted_children": 5039737.2909418885, + "attendance_share": 0.3083075074659248, + "days_per_week": 1.283739352802779, + "hours_per_week": 11.283501114920222 + }, + { + "grouping": "parent_work_status", + "group": "1", + "children": 9966, + "weighted_children": 15397404.080512933, + "attendance_share": 0.3115897123620967, + "days_per_week": 0.9599810926770269, + "hours_per_week": 6.6244519255662295 + }, + { + "grouping": "parent_work_status", + "group": "2", + "children": 18363, + "weighted_children": 30473590.900579363, + "attendance_share": 0.5994515383571952, + "days_per_week": 2.5640085999146067, + "hours_per_week": 17.919932134788446 + }, + { + "grouping": "income_band", + "group": "0", + "children": 94, + "weighted_children": 81378.29635578688, + "attendance_share": 0.29040892685678327, + "days_per_week": 1.0780433457763277, + "hours_per_week": 6.3255131795754185 + }, + { + "grouping": "income_band", + "group": "1", + "children": 3165, + "weighted_children": 6927519.022630059, + "attendance_share": 0.4025097371077471, + "days_per_week": 1.6934463444760064, + "hours_per_week": 12.252209201740305 + }, + { + "grouping": "income_band", + "group": "2", + "children": 6097, + "weighted_children": 10396829.673697237, + "attendance_share": 0.4585389868464392, + "days_per_week": 1.9481109164610568, + "hours_per_week": 14.847718600455874 + }, + { + "grouping": "income_band", + "group": "3", + "children": 9010, + "weighted_children": 14071962.152827512, + "attendance_share": 0.45002653661522485, + "days_per_week": 1.7736004293258292, + "hours_per_week": 13.239778272162871 + }, + { + "grouping": "income_band", + "group": "4", + "children": 7428, + "weighted_children": 13192218.644710096, + "attendance_share": 0.5136530195198358, + "days_per_week": 1.9683914038135206, + "hours_per_week": 14.280673940380375 + }, + { + "grouping": "income_band", + "group": "5", + "children": 6095, + "weighted_children": 7285779.432563353, + "attendance_share": 0.6172477004274525, + "days_per_week": 2.576313155655513, + "hours_per_week": 16.074152133280744 + } + ], + "summary": { + "states_evaluated": 51, + "all_zero_states": { + "baseline": [ + "AL", + "AZ", + "AR", + "CA", + "DC", + "FL", + "HI", + "ID", + "IL", + "IA", + "KS", + "KY", + "LA", + "MD", + "MA", + "MN", + "MS", + "MO", + "NV", + "NY", + "OH", + "OK", + "PA", + "SC", + "SD", + "TN", + "TX", + "VA", + "WV", + "WI", + "WY" + ], + "candidate": [ + "MD", + "NV" + ] + }, + "annual_potential_modeled_benefits": { + "baseline": 2253603947.6976757, + "candidate": 5998228187.26313 + } + } +} diff --git a/experiments/us-childcare-attendance/calendar-review-2.2.1-sensitivity.json b/experiments/us-childcare-attendance/calendar-review-2.2.1-sensitivity.json new file mode 100644 index 000000000..8f2fef044 --- /dev/null +++ b/experiments/us-childcare-attendance/calendar-review-2.2.1-sensitivity.json @@ -0,0 +1,3220 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "seed": 915, + "engine_version": "2.2.1", + "policy_year": 2026, + "code_sha256": { + "validate_us_childcare_sensitivity.py": "59d41bf8b6192f9d4d01976e9a3b464446ade56d67f7493cb611ef3d8c01d07c", + "childcare_sensitivity.py": "a3d370bcaf78b609d81c542dc774b7dab0202ea02f53f80135417320840e06ac" + }, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "assumptions": { + "candidate": {}, + "no_modeled_irregular_hours": { + "include_modeled_irregular_hours": false, + "modeled_day_shift": 0, + "measured_regular_hours_preserved": true + }, + "one_fewer_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": -1, + "measured_regular_hours_preserved": true + }, + "one_more_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": 1, + "measured_regular_hours_preserved": true + } + }, + "changed_children": { + "candidate": 0, + "no_modeled_irregular_hours": 1809, + "one_fewer_day": 5705, + "one_more_day": 6683 + }, + "screens": { + "national_relative_change": 0.1, + "state_relative_change": 0.2 + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "no_modeled_irregular_hours": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_fewer_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_more_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "no_modeled_irregular_hours": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_fewer_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_more_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159527.697095435 + }, + "no_modeled_irregular_hours": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18733.29978438327, + "annual_modeled_benefits": 23159479.981134396 + }, + "one_fewer_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159508.09361713 + }, + "one_more_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 22260467.45209273 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "no_modeled_irregular_hours": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 17289.87330874073, + "annual_modeled_benefits": 50793412.084522665 + }, + "one_fewer_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 47298738.19780214 + }, + "one_more_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 73116589.09583077 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + }, + "no_modeled_irregular_hours": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 165687.58177746536, + "annual_modeled_benefits": 597156813.0898787 + }, + "one_fewer_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + }, + "one_more_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "no_modeled_irregular_hours": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 18502.54616478004, + "annual_modeled_benefits": 236445566.4573961 + }, + "one_fewer_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "one_more_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "no_modeled_irregular_hours": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "one_fewer_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "one_more_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "no_modeled_irregular_hours": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_fewer_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_more_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "no_modeled_irregular_hours": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "one_fewer_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "one_more_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 336693781.677099 + }, + "no_modeled_irregular_hours": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 76833.30946851184, + "annual_modeled_benefits": 320195353.52476215 + }, + "one_fewer_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 60231.18556508084, + "annual_modeled_benefits": 320401507.8530382 + }, + "one_more_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 60231.18556508084, + "annual_modeled_benefits": 324178392.2600601 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "no_modeled_irregular_hours": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_fewer_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_more_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "no_modeled_irregular_hours": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "one_fewer_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "one_more_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "no_modeled_irregular_hours": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_fewer_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_more_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651167.105942965 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + }, + "no_modeled_irregular_hours": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + }, + "one_fewer_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786122.30308777 + }, + "one_more_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786118.53858995 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "no_modeled_irregular_hours": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_fewer_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_more_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + }, + "no_modeled_irregular_hours": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + }, + "one_fewer_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 89338499.34608828 + }, + "one_more_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "no_modeled_irregular_hours": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "one_fewer_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 29101934.00209515 + }, + "one_more_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709158.015559085 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + }, + "no_modeled_irregular_hours": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + }, + "one_fewer_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 157.98810727270265 + }, + "one_more_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 146.34993561158112 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + }, + "no_modeled_irregular_hours": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 69024044.59310351 + }, + "one_fewer_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 37176.93812166341, + "annual_modeled_benefits": 72842353.11263487 + }, + "one_more_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "no_modeled_irregular_hours": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_fewer_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_more_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + }, + "no_modeled_irregular_hours": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + }, + "one_fewer_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + }, + "one_more_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "no_modeled_irregular_hours": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_fewer_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_more_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + }, + "no_modeled_irregular_hours": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + }, + "one_fewer_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + }, + "one_more_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + }, + "no_modeled_irregular_hours": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 7256.951193145127, + "annual_modeled_benefits": 30449886.57411563 + }, + "one_fewer_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 9247129.336387664 + }, + "one_more_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + }, + "no_modeled_irregular_hours": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + }, + "one_fewer_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110017.00214956 + }, + "one_more_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.31673183 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "no_modeled_irregular_hours": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_fewer_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_more_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "no_modeled_irregular_hours": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 750.2732835703686 + }, + "one_fewer_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "one_more_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "no_modeled_irregular_hours": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_fewer_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_more_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "no_modeled_irregular_hours": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_fewer_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_more_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + }, + "no_modeled_irregular_hours": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59644459.68607285 + }, + "one_fewer_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646236.386424586 + }, + "one_more_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647120.40554538 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "no_modeled_irregular_hours": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "one_fewer_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386147637.05533403 + }, + "one_more_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "no_modeled_irregular_hours": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_fewer_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_more_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + }, + "no_modeled_irregular_hours": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 465.6641840398204 + }, + "one_fewer_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 474.0045400403885 + }, + "one_more_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 515.8170557760147 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "no_modeled_irregular_hours": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "one_fewer_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "one_more_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109088290.29528335 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "no_modeled_irregular_hours": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "one_fewer_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 3.0897752604315514 + }, + "one_more_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "no_modeled_irregular_hours": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_fewer_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_more_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "no_modeled_irregular_hours": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "one_fewer_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "one_more_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "no_modeled_irregular_hours": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_fewer_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_more_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "no_modeled_irregular_hours": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 27854.604704734404, + "annual_modeled_benefits": 77020404.93038082 + }, + "one_fewer_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088803.73884593 + }, + "one_more_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + }, + "no_modeled_irregular_hours": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + }, + "one_fewer_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.483422956 + }, + "one_more_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809179.365034073 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + }, + "no_modeled_irregular_hours": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 37178.86130036996, + "annual_modeled_benefits": 76918628.29417688 + }, + "one_fewer_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178652.73669359 + }, + "one_more_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 58319.75094880746, + "annual_modeled_benefits": 139207804.7182324 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "no_modeled_irregular_hours": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_fewer_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_more_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "no_modeled_irregular_hours": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "one_fewer_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "one_more_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + }, + "no_modeled_irregular_hours": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 707106120.8814734 + }, + "one_fewer_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 723295768.2248663 + }, + "one_more_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 32213.82430987344, + "annual_modeled_benefits": 735348229.2533482 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 249203866.21813098 + }, + "no_modeled_irregular_hours": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 45187.34378196369, + "annual_modeled_benefits": 249203861.9343761 + }, + "one_fewer_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 218587967.72536415 + }, + "one_more_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 45187.52193577867, + "annual_modeled_benefits": 254330473.5103389 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "no_modeled_irregular_hours": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_fewer_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_more_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.243193686 + }, + "no_modeled_irregular_hours": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329923.422902562 + }, + "one_fewer_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329930.062846346 + }, + "one_more_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 10521487.168013036 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "no_modeled_irregular_hours": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "one_fewer_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284617.12617347 + }, + "one_more_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 33918.61217102362, + "annual_modeled_benefits": 210174118.98339957 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "no_modeled_irregular_hours": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 2962.834582739495, + "annual_modeled_benefits": 18103300.895859934 + }, + "one_fewer_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "one_more_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18834318.50135846 + } + } + ], + "production_ready": false, + "interpretation": "assumption stress tests, not confidence intervals; potential modeled benefits, not calibrated spending", + "summary": { + "states_evaluated": 51, + "annual_potential_modeled_benefits": { + "baseline": 2253603947.6976757, + "candidate": 5998228187.26313, + "no_modeled_irregular_hours": 5792223617.337031, + "one_fewer_day": 5934455676.753909, + "one_more_day": 6010100963.719955 + }, + "sensitivity_flags": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.3599643633557116, + "absolute_change": -43260036.59620613, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "IA", + "relative_change": 0.5999990332912062, + "absolute_change": 33501903.518702976, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "KS", + "relative_change": -0.49350560992419995, + "absolute_change": -28355630.330927107, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "MS", + "relative_change": -0.6963165011569457, + "absolute_change": -21202761.328125004, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_more_day", + "state": "AR", + "relative_change": 0.43948945692098534, + "absolute_change": 22323171.50997054, + "flagged": true + } + ], + "comparisons": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "US", + "relative_change": -0.03434423691375011, + "absolute_change": -206004569.92609882, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AZ", + "relative_change": -2.0603166723927034e-06, + "absolute_change": -47.715961039066315, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AR", + "relative_change": -1.0830808059296869e-07, + "absolute_change": -5.501337565481663, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CA", + "relative_change": -0.1774859678920697, + "absolute_change": -128857321.3553313, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CO", + "relative_change": 1.5282148622892612e-07, + "absolute_change": 36.13395735621452, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "FL", + "relative_change": -0.04900128559000059, + "absolute_change": -16498428.152336836, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "LA", + "relative_change": -0.0524188932261243, + "absolute_change": -3818315.91796875, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MN", + "relative_change": -2.262688775133818e-09, + "absolute_change": -0.6219009757041931, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MS", + "relative_change": -1.343320763490555e-07, + "absolute_change": -4.090397037565708, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NE", + "relative_change": -0.002700483876552961, + "absolute_change": -2.0315871736966074, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NM", + "relative_change": -2.9711291489026368e-05, + "absolute_change": -1772.1665807962418, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ND", + "relative_change": -0.011427064224842841, + "absolute_change": -5.382682800292969, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SC", + "relative_change": -0.0008873514588288169, + "absolute_change": -68404.86783382297, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SD", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.3599643633557116, + "absolute_change": -43260036.59620613, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VT", + "relative_change": -0.01873457248239016, + "absolute_change": -13500252.330206513, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VA", + "relative_change": -1.7189760938327763e-08, + "absolute_change": -4.283754885196686, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WV", + "relative_change": -5.166482526594711e-07, + "absolute_change": -4.820291124284267, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WY", + "relative_change": -1.2466196861638054e-08, + "absolute_change": -0.22567931562662125, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "US", + "relative_change": -0.01063189137162839, + "absolute_change": -63772510.50922108, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AZ", + "relative_change": -8.464541488685182e-07, + "absolute_change": -19.60347830504179, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AR", + "relative_change": -0.06880181634068526, + "absolute_change": -3494679.3880580887, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "FL", + "relative_change": -0.04838899531469712, + "absolute_change": -16292273.824060798, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IL", + "relative_change": -6.29279612809565e-07, + "absolute_change": -123.8335727751255, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IA", + "relative_change": 0.5999990332912062, + "absolute_change": 33501903.518702976, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "KS", + "relative_change": -0.49350560992419995, + "absolute_change": -28355630.330927107, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "KY", + "relative_change": 0.06803920743236715, + "absolute_change": 10.06459831040911, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "LA", + "relative_change": -1.015677875316422e-07, + "absolute_change": -7.398437395691872, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MS", + "relative_change": -0.6963165011569457, + "absolute_change": -21202761.328125004, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "MO", + "relative_change": 5.114562408698159e-08, + "absolute_change": 2.256034232676029, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NM", + "relative_change": 7.601102031722338e-08, + "absolute_change": 4.533770941197872, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NY", + "relative_change": -6.160768878729368e-06, + "absolute_change": -2378.981001317501, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ND", + "relative_change": 0.006278936149423715, + "absolute_change": 2.957673200275167, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SC", + "relative_change": -7.860244217741888e-08, + "absolute_change": -6.059368714690208, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SD", + "relative_change": -7.935147357416053e-08, + "absolute_change": -0.46096683014184237, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TN", + "relative_change": -1.0113017502190059e-07, + "absolute_change": -12.153689414262772, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VT", + "relative_change": 0.0037321277096120254, + "absolute_change": 2689395.0131863356, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VA", + "relative_change": -0.122854829491162, + "absolute_change": -30615898.492766827, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WV", + "relative_change": 1.9503393942456014e-07, + "absolute_change": 1.81965265981853, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WI", + "relative_change": -1.474560751887755e-07, + "absolute_change": -36.7585366666317, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "US", + "relative_change": 0.0019793805914280435, + "absolute_change": 11872776.456825256, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AZ", + "relative_change": -0.038820318650775495, + "absolute_change": -899060.2450027056, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AR", + "relative_change": 0.43948945692098534, + "absolute_change": 22323171.50997054, + "flagged": true + }, + { + "scenario": "one_more_day", + "state": "CA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "FL", + "relative_change": -0.037171430237584874, + "absolute_change": -12515389.417038918, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ID", + "relative_change": -8.714356196712921e-06, + "absolute_change": -92.81887292861938, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IL", + "relative_change": -6.484094955654378e-07, + "absolute_change": -127.59807059168816, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KS", + "relative_change": 0.02178292270244223, + "absolute_change": 1251593.6825368255, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KY", + "relative_change": -0.010637750292372622, + "absolute_change": -1.5735733507124223, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "LA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MO", + "relative_change": -9.734376568026846e-09, + "absolute_change": -0.4293834939599037, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NM", + "relative_change": 1.4897049891328236e-05, + "absolute_change": 888.5528917387128, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ND", + "relative_change": 0.09504402234161904, + "absolute_change": 44.77018893590139, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OH", + "relative_change": 1.9287845264817622e-05, + "absolute_change": 2104.037481069565, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SD", + "relative_change": 2.445516903920343e-07, + "absolute_change": 1.4206442860886455, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TN", + "relative_change": 0.15834041628941534, + "absolute_change": 19029139.827849388, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VT", + "relative_change": 0.020457570998109725, + "absolute_change": 14741856.041668296, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VA", + "relative_change": 0.020571941238345593, + "absolute_change": 5126607.2922079265, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WV", + "relative_change": 0.1277136215585162, + "absolute_change": 1191558.9248193502, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WI", + "relative_change": -0.1568910652614762, + "absolute_change": -39110534.90131056, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WY", + "relative_change": 0.04038033587970585, + "absolute_change": 731017.3798192106, + "flagged": false + } + ] + } +} diff --git a/experiments/us-childcare-attendance/calendar-review-2.2.1-source-stage.json b/experiments/us-childcare-attendance/calendar-review-2.2.1-source-stage.json new file mode 100644 index 000000000..3df1fcbed --- /dev/null +++ b/experiments/us-childcare-attendance/calendar-review-2.2.1-source-stage.json @@ -0,0 +1,1994 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "source_child_count": 11745, + "source_attrition": [ + { + "status": "age_out_of_scope", + "n": 134, + "child_weight": 633390.6468148227 + }, + { + "status": "ambiguous_calendar", + "n": 882, + "child_weight": 3918044.035953992 + }, + { + "status": "complete", + "n": 7460, + "child_weight": 32249785.682044942 + }, + { + "status": "missing_calendar", + "n": 3095, + "child_weight": 14061828.648228496 + }, + { + "status": "partial_calendar", + "n": 174, + "child_weight": 847769.9869632281 + } + ], + "under13_complete_weight_share": 0.6313901604255374, + "seed": 915, + "evaluation_design": "fixed household diagnostic split; not final population certification", + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "training_children": 5988, + "heldout_children": 1472, + "unsupported_heldout_children": 30, + "unsupported_heldout_weight": 153817.788204796, + "household_overlap": 0, + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1442, + "observed": { + "participation": 0.47956011362829676, + "days_per_week": 1.94220990502671, + "hours_per_week": 14.670259928481958 + }, + "predicted": { + "participation": 0.4547372440086608, + "days_per_week": 1.7986704844660344, + "hours_per_week": 13.001295500494344 + } + }, + { + "grouping": "age", + "group": "0", + "n": 87, + "observed": { + "participation": 0.4749769477860246, + "days_per_week": 1.8705229048944185, + "hours_per_week": 15.234832555457738 + }, + "predicted": { + "participation": 0.21347668238234924, + "days_per_week": 0.7762397588546358, + "hours_per_week": 6.331944692101247 + } + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed": { + "participation": 0.6026124718926947, + "days_per_week": 2.2004884644301415, + "hours_per_week": 16.058889950232714 + }, + "predicted": { + "participation": 0.5817111215090339, + "days_per_week": 2.3867359768549528, + "hours_per_week": 20.29503642359935 + } + }, + { + "grouping": "age", + "group": "2", + "n": 107, + "observed": { + "participation": 0.6827728698846081, + "days_per_week": 3.2446932739752876, + "hours_per_week": 32.643365116161064 + }, + "predicted": { + "participation": 0.721108922787169, + "days_per_week": 2.9279814704204314, + "hours_per_week": 25.4484971084278 + } + }, + { + "grouping": "age", + "group": "3", + "n": 105, + "observed": { + "participation": 0.5478113606887365, + "days_per_week": 2.4737318877881727, + "hours_per_week": 18.774571500322175 + }, + "predicted": { + "participation": 0.7525857598038783, + "days_per_week": 3.556893850206531, + "hours_per_week": 31.34897414405891 + } + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed": { + "participation": 0.6853382748904278, + "days_per_week": 3.133535876664359, + "hours_per_week": 22.72675588135229 + }, + "predicted": { + "participation": 0.6264622224091229, + "days_per_week": 2.555534029105278, + "hours_per_week": 22.558086951188297 + } + }, + { + "grouping": "age", + "group": "5", + "n": 109, + "observed": { + "participation": 0.45965467004039157, + "days_per_week": 2.204288116474054, + "hours_per_week": 16.671100530443887 + }, + "predicted": { + "participation": 0.5197893305690322, + "days_per_week": 2.0628456061601703, + "hours_per_week": 14.660711473842793 + } + }, + { + "grouping": "age", + "group": "6", + "n": 121, + "observed": { + "participation": 0.37767600901948256, + "days_per_week": 1.4233675361325482, + "hours_per_week": 11.368361596236443 + }, + "predicted": { + "participation": 0.3812603191662228, + "days_per_week": 1.344172188177959, + "hours_per_week": 6.687190574415363 + } + }, + { + "grouping": "age", + "group": "7", + "n": 121, + "observed": { + "participation": 0.2903318905157446, + "days_per_week": 0.9137564001326794, + "hours_per_week": 4.425088819370026 + }, + "predicted": { + "participation": 0.3316072853951072, + "days_per_week": 1.3098426672238894, + "hours_per_week": 7.381236186215511 + } + }, + { + "grouping": "age", + "group": "8", + "n": 136, + "observed": { + "participation": 0.33161676756925296, + "days_per_week": 1.2251387657453359, + "hours_per_week": 6.71788070980216 + }, + "predicted": { + "participation": 0.35465278512495324, + "days_per_week": 1.3642638058954168, + "hours_per_week": 9.725344329820171 + } + }, + { + "grouping": "age", + "group": "9", + "n": 101, + "observed": { + "participation": 0.4569510479920196, + "days_per_week": 1.674346958462562, + "hours_per_week": 7.805739183708693 + }, + "predicted": { + "participation": 0.396876880067868, + "days_per_week": 1.3966673442424729, + "hours_per_week": 8.701466574792914 + } + }, + { + "grouping": "age", + "group": "10", + "n": 105, + "observed": { + "participation": 0.45503949077471245, + "days_per_week": 1.5782906105470733, + "hours_per_week": 12.426848636692846 + }, + "predicted": { + "participation": 0.17752112044794535, + "days_per_week": 0.6469841431239514, + "hours_per_week": 3.596759032644189 + } + }, + { + "grouping": "age", + "group": "11", + "n": 119, + "observed": { + "participation": 0.41662598750149954, + "days_per_week": 1.8643111020323588, + "hours_per_week": 12.941021091087745 + }, + "predicted": { + "participation": 0.4008574857176407, + "days_per_week": 1.339718142783517, + "hours_per_week": 7.024111200026524 + } + }, + { + "grouping": "age", + "group": "12", + "n": 103, + "observed": { + "participation": 0.46408861423609865, + "days_per_week": 1.4945247924023801, + "hours_per_week": 11.114322042194766 + }, + "predicted": { + "participation": 0.43591150819042584, + "days_per_week": 1.7008936429461807, + "hours_per_week": 7.10555160431239 + } + }, + { + "grouping": "region", + "group": "1", + "n": 187, + "observed": { + "participation": 0.5340539220646277, + "days_per_week": 2.4009930157522064, + "hours_per_week": 14.79000641069395 + }, + "predicted": { + "participation": 0.6359603067185889, + "days_per_week": 2.7160385976574153, + "hours_per_week": 21.24487930853725 + } + }, + { + "grouping": "region", + "group": "2", + "n": 335, + "observed": { + "participation": 0.46285701128880247, + "days_per_week": 1.8453473655274784, + "hours_per_week": 15.352184287431765 + }, + "predicted": { + "participation": 0.41283532735257467, + "days_per_week": 1.4868890394545802, + "hours_per_week": 9.329182591248077 + } + }, + { + "grouping": "region", + "group": "3", + "n": 470, + "observed": { + "participation": 0.4328632075166446, + "days_per_week": 1.8236781494293404, + "hours_per_week": 15.35364160426729 + }, + "predicted": { + "participation": 0.4184231306502971, + "days_per_week": 1.5947483523709618, + "hours_per_week": 10.342311458512322 + } + }, + { + "grouping": "region", + "group": "4", + "n": 450, + "observed": { + "participation": 0.5171827642556369, + "days_per_week": 1.8475690827448323, + "hours_per_week": 13.04393396272379 + }, + "predicted": { + "participation": 0.4063739455351018, + "days_per_week": 1.670368549893466, + "hours_per_week": 13.748230642009998 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 13, + "observed": { + "participation": 0.253295875991658, + "days_per_week": 0.8671327195533685, + "hours_per_week": 3.515415042668932 + }, + "predicted": { + "participation": 0.12949135028799522, + "days_per_week": 0.5134069370080796, + "hours_per_week": 3.2849650228565768 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 150, + "observed": { + "participation": 0.2939873489805854, + "days_per_week": 1.1857568669020946, + "hours_per_week": 10.001769026104405 + }, + "predicted": { + "participation": 0.20052896295212805, + "days_per_week": 0.7861821143968067, + "hours_per_week": 6.547539835013359 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 543, + "observed": { + "participation": 0.2512596429034898, + "days_per_week": 0.8664048151368835, + "hours_per_week": 5.612021341518408 + }, + "predicted": { + "participation": 0.30234830539829555, + "days_per_week": 0.9663278582430083, + "hours_per_week": 5.557806485223704 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 736, + "observed": { + "participation": 0.6462583482271946, + "days_per_week": 2.714084969528804, + "hours_per_week": 21.02161873348801 + }, + "predicted": { + "participation": 0.5856324643226721, + "days_per_week": 2.4572831629804637, + "hours_per_week": 18.54158986890966 + } + } + ], + "production_ready": false, + "limitations": [ + "Only complete, unambiguous calendars enter this holdout; source selection remains unvalidated.", + "The diagnostic matches age and caller-specified covariates; it is not a nationally validated fitted model.", + "Sibling assignments, mixed providers, older-child care and scalar monthly conversion need validation.", + "No full Microcosm population build or state CCDF distribution is certified by this source-only report." + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "selection_and_cross_validation": { + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "seed": 271828, + "design": "five-fold household-separated conditional-expectation diagnostic", + "coverage": [ + { + "grouping": "questionnaire_version", + "group": "1", + "n": 8516, + "weight": 37015599.704962164, + "usable_weight_share": 0.8712484989868113 + }, + { + "grouping": "questionnaire_version", + "group": "2", + "n": 1716, + "weight": 7945306.22844611, + "usable_weight_share": 0.0 + }, + { + "grouping": "questionnaire_version", + "group": "3", + "n": 1379, + "weight": 6116522.419782387, + "usable_weight_share": 0.0 + }, + { + "grouping": "age", + "group": "0", + "n": 709, + "weight": 3159547.2696341733, + "usable_weight_share": 0.6556513621184303 + }, + { + "grouping": "age", + "group": "1", + "n": 856, + "weight": 3710311.739747545, + "usable_weight_share": 0.6518631977426506 + }, + { + "grouping": "age", + "group": "2", + "n": 906, + "weight": 4112848.861546397, + "usable_weight_share": 0.6567885484115844 + }, + { + "grouping": "age", + "group": "3", + "n": 927, + "weight": 4191268.180093972, + "usable_weight_share": 0.5836151261043054 + }, + { + "grouping": "age", + "group": "4", + "n": 890, + "weight": 3761621.476875998, + "usable_weight_share": 0.6069150244376095 + }, + { + "grouping": "age", + "group": "5", + "n": 884, + "weight": 3759953.5032736305, + "usable_weight_share": 0.6441250249176611 + }, + { + "grouping": "age", + "group": "6", + "n": 954, + "weight": 4259102.717846392, + "usable_weight_share": 0.6213110691400029 + }, + { + "grouping": "age", + "group": "7", + "n": 944, + "weight": 4191490.05150999, + "usable_weight_share": 0.6014263712726516 + }, + { + "grouping": "age", + "group": "8", + "n": 925, + "weight": 3969583.419307067, + "usable_weight_share": 0.6776062606911202 + }, + { + "grouping": "age", + "group": "9", + "n": 921, + "weight": 4036325.188020362, + "usable_weight_share": 0.6661696977311178 + }, + { + "grouping": "age", + "group": "10", + "n": 884, + "weight": 3707790.894951107, + "usable_weight_share": 0.6220814618665044 + }, + { + "grouping": "age", + "group": "11", + "n": 908, + "weight": 4069233.422108399, + "usable_weight_share": 0.6084497314858798 + }, + { + "grouping": "age", + "group": "12", + "n": 903, + "weight": 4148351.6282756263, + "usable_weight_share": 0.6217216150291205 + }, + { + "grouping": "region", + "group": "1", + "n": 1468, + "weight": 8153076.12536276, + "usable_weight_share": 0.6401722331669089 + }, + { + "grouping": "region", + "group": "2", + "n": 2680, + "weight": 10714972.208999828, + "usable_weight_share": 0.6330942004076335 + }, + { + "grouping": "region", + "group": "3", + "n": 4275, + "weight": 20022339.81304313, + "usable_weight_share": 0.6249460927360144 + }, + { + "grouping": "region", + "group": "4", + "n": 3188, + "weight": 12187040.205784943, + "usable_weight_share": 0.634603874645437 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 188, + "weight": 819575.738509818, + "usable_weight_share": 0.646736557730207 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1470, + "weight": 5301911.400981113, + "usable_weight_share": 0.6698890755679009 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 3380, + "weight": 14009836.058523985, + "usable_weight_share": 0.7212981495115555 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 6573, + "weight": 30946105.155175745, + "usable_weight_share": 0.5836849144158337 + }, + { + "grouping": "income_band", + "group": "0", + "n": 120, + "weight": 497291.815717528, + "usable_weight_share": 0.602001589054288 + }, + { + "grouping": "income_band", + "group": "1", + "n": 2057, + "weight": 7466809.932490364, + "usable_weight_share": 0.6413274998297077 + }, + { + "grouping": "income_band", + "group": "2", + "n": 2904, + "weight": 10880663.16706181, + "usable_weight_share": 0.6487048847817203 + }, + { + "grouping": "income_band", + "group": "3", + "n": 2558, + "weight": 11325516.809686286, + "usable_weight_share": 0.6401496463974632 + }, + { + "grouping": "income_band", + "group": "4", + "n": 3134, + "weight": 15757011.988844048, + "usable_weight_share": 0.6309648019074636 + }, + { + "grouping": "income_band", + "group": "5", + "n": 838, + "weight": 5150134.639390623, + "usable_weight_share": 0.5652783862122169 + } + ], + "cross_validation": [ + { + "grouping": "all", + "group": "all", + "n": 7460, + "observed": { + "participation": 0.46514845107061753, + "days": 1.8750709563867467, + "hours": 14.240422572045741 + }, + "expected": { + "participation": 0.4654844290416027, + "days": 1.878527294011518, + "hours": 14.081114700859393 + } + }, + { + "grouping": "age", + "group": "0", + "n": 458, + "observed": { + "participation": 0.40007662898763713, + "days": 1.5207410031354076, + "hours": 12.391341719561105 + }, + "expected": { + "participation": 0.42043937266485903, + "days": 1.5581186938006875, + "hours": 12.778280226573155 + } + }, + { + "grouping": "age", + "group": "1", + "n": 585, + "observed": { + "participation": 0.5768442364096245, + "days": 2.3247221464109487, + "hours": 18.93194004904792 + }, + "expected": { + "participation": 0.5636534387398192, + "days": 2.2787632897750942, + "hours": 18.46136873433321 + } + }, + { + "grouping": "age", + "group": "2", + "n": 599, + "observed": { + "participation": 0.6119472630516792, + "days": 2.661365110302835, + "hours": 23.568671427585098 + }, + "expected": { + "participation": 0.615171451571855, + "days": 2.6668583554129937, + "hours": 24.008204825875836 + } + }, + { + "grouping": "age", + "group": "3", + "n": 597, + "observed": { + "participation": 0.6106570906701301, + "days": 2.6641302585227766, + "hours": 21.96346327818111 + }, + "expected": { + "participation": 0.6452189769582379, + "days": 2.8385499518769803, + "hours": 23.234965023661523 + } + }, + { + "grouping": "age", + "group": "4", + "n": 550, + "observed": { + "participation": 0.7046928755645677, + "days": 3.1467359328380096, + "hours": 25.30894736806148 + }, + "expected": { + "participation": 0.7168364294128767, + "days": 3.1704359925035326, + "hours": 25.23962440956216 + } + }, + { + "grouping": "age", + "group": "5", + "n": 581, + "observed": { + "participation": 0.5580840489623686, + "days": 2.527957605747102, + "hours": 20.351246817207983 + }, + "expected": { + "participation": 0.5580783794454302, + "days": 2.4817744855985993, + "hours": 19.696005730111843 + } + }, + { + "grouping": "age", + "group": "6", + "n": 611, + "observed": { + "participation": 0.38410943637037254, + "days": 1.5233703763582376, + "hours": 9.272995362839891 + }, + "expected": { + "participation": 0.3704517476571586, + "days": 1.4435320487985335, + "hours": 8.308626409800924 + } + }, + { + "grouping": "age", + "group": "7", + "n": 586, + "observed": { + "participation": 0.34047335603475937, + "days": 1.267585660002729, + "hours": 8.978033563109225 + }, + "expected": { + "participation": 0.32048663598972876, + "days": 1.2092330340598734, + "hours": 7.976819608666644 + } + }, + { + "grouping": "age", + "group": "8", + "n": 611, + "observed": { + "participation": 0.37000227568041516, + "days": 1.4234519706143929, + "hours": 8.822905923973602 + }, + "expected": { + "participation": 0.37441818500322505, + "days": 1.4678570808250095, + "hours": 8.581931573672641 + } + }, + { + "grouping": "age", + "group": "9", + "n": 590, + "observed": { + "participation": 0.3862968300759907, + "days": 1.373131779820256, + "hours": 9.825473717875598 + }, + "expected": { + "participation": 0.3658218612710925, + "days": 1.2745107095149821, + "hours": 9.212380152023707 + } + }, + { + "grouping": "age", + "group": "10", + "n": 541, + "observed": { + "participation": 0.39185689727904155, + "days": 1.3647219850376735, + "hours": 8.468134791861964 + }, + "expected": { + "participation": 0.3879904743599543, + "days": 1.313066433397897, + "hours": 7.6091158562940935 + } + }, + { + "grouping": "age", + "group": "11", + "n": 565, + "observed": { + "participation": 0.3768238596848206, + "days": 1.3633597325953704, + "hours": 8.143708694787577 + }, + "expected": { + "participation": 0.37027421084947587, + "days": 1.3896706621501198, + "hours": 8.180743029734462 + } + }, + { + "grouping": "age", + "group": "12", + "n": 586, + "observed": { + "participation": 0.35838594666001994, + "days": 1.3239431239565607, + "hours": 10.203528792841063 + }, + "expected": { + "participation": 0.37173621674013835, + "days": 1.447846572682685, + "hours": 10.94364602719194 + } + }, + { + "grouping": "region", + "group": "1", + "n": 929, + "observed": { + "participation": 0.4988625902951673, + "days": 2.101321934458425, + "hours": 14.697745432365767 + }, + "expected": { + "participation": 0.5341920187102704, + "days": 2.234373766910041, + "hours": 15.515545077772881 + } + }, + { + "grouping": "region", + "group": "2", + "n": 1654, + "observed": { + "participation": 0.495642471526947, + "days": 1.9378635600824832, + "hours": 14.438707404977198 + }, + "expected": { + "participation": 0.48657470779470846, + "days": 1.9053160452954483, + "hours": 13.830397580793818 + } + }, + { + "grouping": "region", + "group": "3", + "n": 2752, + "observed": { + "participation": 0.4310162776090592, + "days": 1.7482660126296112, + "hours": 14.16760866653331 + }, + "expected": { + "participation": 0.4254047590539008, + "days": 1.728432288982956, + "hours": 13.771990873343658 + } + }, + { + "grouping": "region", + "group": "4", + "n": 2125, + "observed": { + "participation": 0.470872102608504, + "days": 1.872465315119329, + "hours": 13.875678601794997 + }, + "expected": { + "participation": 0.46546295898430057, + "days": 1.857723153971244, + "hours": 13.833112421687238 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 122, + "observed": { + "participation": 0.34614706740243706, + "days": 1.1849281571656916, + "hours": 9.788566562075097 + }, + "expected": { + "participation": 0.373389275855184, + "days": 1.462386368172592, + "hours": 12.574282386813668 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1016, + "observed": { + "participation": 0.3096578455166947, + "days": 1.244408501915661, + "hours": 10.193456359940383 + }, + "expected": { + "participation": 0.2882996076692788, + "days": 1.1762845771341546, + "hours": 10.282450861819337 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 2418, + "observed": { + "participation": 0.2864480974767621, + "days": 0.9529380212414282, + "hours": 6.42952602558992 + }, + "expected": { + "participation": 0.29059369126489437, + "days": 0.9858655333196232, + "hours": 6.764091600272923 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 3904, + "observed": { + "participation": 0.5991891288528669, + "days": 2.535220374688929, + "hours": 19.536646150926547 + }, + "expected": { + "participation": 0.6008699918772364, + "days": 2.528223299633135, + "hours": 18.96579429859326 + } + }, + { + "grouping": "income_band", + "group": "0", + "n": 76, + "observed": { + "participation": 0.19070472825408744, + "days": 0.8431547354115522, + "hours": 5.587881885800679 + }, + "expected": { + "participation": 0.25300464764173586, + "days": 0.9988467887623982, + "hours": 7.43593331362669 + } + }, + { + "grouping": "income_band", + "group": "1", + "n": 1378, + "observed": { + "participation": 0.35684525807866907, + "days": 1.5280931021110342, + "hours": 12.098088591903252 + }, + "expected": { + "participation": 0.3773650597658377, + "days": 1.627338456412184, + "hours": 12.706694579809993 + } + }, + { + "grouping": "income_band", + "group": "2", + "n": 1922, + "observed": { + "participation": 0.4194011939997811, + "days": 1.7068232976926216, + "hours": 13.491053608546364 + }, + "expected": { + "participation": 0.4119472083085292, + "days": 1.7030609232530567, + "hours": 13.262000601507708 + } + }, + { + "grouping": "income_band", + "group": "3", + "n": 1632, + "observed": { + "participation": 0.4246978564102466, + "days": 1.7238735833385048, + "hours": 14.076591113511121 + }, + "expected": { + "participation": 0.4255788471352766, + "days": 1.704797441331804, + "hours": 13.264862389978802 + } + }, + { + "grouping": "income_band", + "group": "4", + "n": 1977, + "observed": { + "participation": 0.5221816116808028, + "days": 1.9968956951019248, + "hours": 14.35832226780555 + }, + "expected": { + "participation": 0.519705526319959, + "days": 1.9988673657729517, + "hours": 14.55106672367201 + } + }, + { + "grouping": "income_band", + "group": "5", + "n": 475, + "observed": { + "participation": 0.6883939484468758, + "days": 2.9203317046618578, + "hours": 20.476264713601847 + }, + "expected": { + "participation": 0.6762909354194406, + "days": 2.8292574855634296, + "hours": 19.43897806431697 + } + } + ], + "matching_levels": { + "age,region,parent_work_status,income_band": 7278, + "age,parent_work_status,income_band": 150, + "age,parent_work_status": 32 + }, + "sibling_validation": [ + { + "fold": 0, + "training_rho": 0.7504775690168417, + "households": 351, + "weight": 1627200.3809817089, + "observed_both": 0.3346110552302055, + "independent_both": 0.26023741618799584, + "coupled_both": 0.3228394147816889 + }, + { + "fold": 1, + "training_rho": 0.8095401305423818, + "households": 367, + "weight": 1640369.17044373, + "observed_both": 0.308478237709926, + "independent_both": 0.2538314963354507, + "coupled_both": 0.31777192913378116 + }, + { + "fold": 2, + "training_rho": 0.7176223341350483, + "households": 443, + "weight": 1849383.6729044288, + "observed_both": 0.3831182075641927, + "independent_both": 0.260592140353083, + "coupled_both": 0.3144997720634755 + }, + { + "fold": 3, + "training_rho": 0.8946850037821993, + "households": 398, + "weight": 1567825.4608580691, + "observed_both": 0.2954259187077949, + "independent_both": 0.2551329149477867, + "coupled_both": 0.3186981148017382 + }, + { + "fold": 4, + "training_rho": 0.8022505236076816, + "households": 382, + "weight": 1644777.832273697, + "observed_both": 0.3129856591731878, + "independent_both": 0.2695651435301845, + "coupled_both": 0.3411406780146054 + } + ], + "questionnaire_transport": [ + { + "questionnaire_version": 2, + "scored_children": 1690, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.4166998042800092, + "regular_hours_per_week": 12.379208437570716 + }, + "expected": { + "regular_participation": 0.429264128691119, + "regular_hours_per_week": 14.800674181069196 + } + }, + { + "questionnaire_version": 3, + "scored_children": 1356, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.38447915314763226, + "regular_hours_per_week": 10.163827277430636 + }, + "expected": { + "regular_participation": 0.40786334693307874, + "regular_hours_per_week": 13.921490221932158 + } + } + ], + "assumptions": [ + "Calendar selection is ignorable conditional on matching fields; not identified from these data.", + "Main reference-week schedules transfer to May/fall typical weeks; summer attendance is not observed.", + "Regular-care comparisons cannot validate days or irregular care in instruments without calendars." + ], + "production_ready": false + }, + "masked_calendar_validation": { + "seed": 161803, + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1581, + "observed_days": 1.7176113066572272, + "imputed_days": 1.7931671544992573, + "observed_hours": 13.097316141278531, + "imputed_hours": 13.161738723028055 + }, + { + "grouping": "age", + "group": "0", + "n": 104, + "observed_days": 1.841126169101498, + "imputed_days": 1.757972840808916, + "observed_hours": 17.766331967357488, + "imputed_hours": 18.04127357697798 + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed_days": 2.37969546844872, + "imputed_days": 2.466724275790995, + "observed_hours": 19.829328945193588, + "imputed_hours": 19.122886717221327 + }, + { + "grouping": "age", + "group": "2", + "n": 141, + "observed_days": 2.4591773765558673, + "imputed_days": 2.5150278262191854, + "observed_hours": 22.536051090399965, + "imputed_hours": 22.20070627284854 + }, + { + "grouping": "age", + "group": "3", + "n": 123, + "observed_days": 2.3763627081083216, + "imputed_days": 2.276649667113583, + "observed_hours": 20.251977914853146, + "imputed_hours": 19.002839959330025 + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed_days": 3.137397964036117, + "imputed_days": 3.2507274717120613, + "observed_hours": 23.68360614421261, + "imputed_hours": 24.493916641155817 + }, + { + "grouping": "age", + "group": "5", + "n": 113, + "observed_days": 2.2242008794879244, + "imputed_days": 2.6293843819994196, + "observed_hours": 15.760856040214335, + "imputed_hours": 17.46780844312604 + }, + { + "grouping": "age", + "group": "6", + "n": 130, + "observed_days": 1.1830651060264654, + "imputed_days": 1.4124105587435267, + "observed_hours": 7.042175416036001, + "imputed_hours": 6.515374725210956 + }, + { + "grouping": "age", + "group": "7", + "n": 131, + "observed_days": 1.4073709771017413, + "imputed_days": 1.2967131682238184, + "observed_hours": 10.083939201157254, + "imputed_hours": 9.618996783594744 + }, + { + "grouping": "age", + "group": "8", + "n": 126, + "observed_days": 1.057579033652681, + "imputed_days": 1.0820232296805903, + "observed_hours": 8.256788366496435, + "imputed_hours": 6.9299612873656375 + }, + { + "grouping": "age", + "group": "9", + "n": 118, + "observed_days": 1.4349301372689758, + "imputed_days": 1.2570443106530333, + "observed_hours": 10.733568940718998, + "imputed_hours": 11.528485757775337 + }, + { + "grouping": "age", + "group": "10", + "n": 124, + "observed_days": 1.2058459898990705, + "imputed_days": 1.2067301677274491, + "observed_hours": 4.1774445652404655, + "imputed_hours": 4.342805855105561 + }, + { + "grouping": "age", + "group": "11", + "n": 120, + "observed_days": 0.9483350107584563, + "imputed_days": 1.2273618861530076, + "observed_hours": 5.566349482238208, + "imputed_hours": 6.3416466466885835 + }, + { + "grouping": "age", + "group": "12", + "n": 123, + "observed_days": 1.1181905452629102, + "imputed_days": 1.3377626847259545, + "observed_hours": 8.079018679724042, + "imputed_hours": 9.278796503817835 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 33, + "observed_days": 1.5430497710033617, + "imputed_days": 1.3274872915746017, + "observed_hours": 12.754941207431992, + "imputed_hours": 12.147679308677683 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 233, + "observed_days": 1.3163214930511598, + "imputed_days": 1.4661772450887058, + "observed_hours": 9.57367968242547, + "imputed_hours": 9.490248507911081 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 529, + "observed_days": 0.8935323002915262, + "imputed_days": 1.0535312696492358, + "observed_hours": 6.370457810059637, + "imputed_hours": 7.0815725747740155 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 786, + "observed_days": 2.3152602893352414, + "imputed_days": 2.332856367184494, + "observed_hours": 17.994069548324816, + "imputed_hours": 17.722049473088507 + } + ], + "regular_hours_preserved": true, + "limitations": "Tests reconstruction where calendars exist; not a direct test of unobserved days in the other questionnaire instruments." + }, + "sibling_schedule_validation": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,region,parent_work_status,income_band": 4682, + "age,parent_work_status,income_band": 87, + "age,parent_work_status": 20 + }, + "training_rho": [ + 0.7504775690168417, + 0.8095401305423818, + 0.7176223341350483, + 0.8946850037821993, + 0.8022505236076816 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.2599357218299179, + "correlation": 0.14356484664250233 + }, + "days": { + "joint_product": 4.562554958882675, + "correlation": 0.1514064619097721 + }, + "weekly_hours": { + "joint_product": 274.5482618143516, + "correlation": 0.1290575794049498 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3228241594928079, + "correlation": 0.39662161163709453 + }, + "days": { + "joint_product": 6.168391228908872, + "correlation": 0.44224735414975214 + }, + "weekly_hours": { + "joint_product": 393.50536137310974, + "correlation": 0.35635277128820114 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.26594831103314626, + "correlation": 0.1768906778018396 + }, + "days": { + "joint_product": 4.755017625871223, + "correlation": 0.1796772530434437 + }, + "weekly_hours": { + "joint_product": 288.78160178706247, + "correlation": 0.15022942733210107 + } + }, + "coupled": { + "participation": { + "joint_product": 0.32461663032314914, + "correlation": 0.41320572283269663 + }, + "days": { + "joint_product": 6.243213912824172, + "correlation": 0.44765187465853035 + }, + "weekly_hours": { + "joint_product": 406.53469377347056, + "correlation": 0.3715598543103152 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 4.742171858563125, + "sd_total_weekly_hours": 43.69183090611876, + "all_children_attend": 0.10870430564775237 + }, + "coupled": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 5.826012333228911, + "sd_total_weekly_hours": 52.0732028760817, + "all_children_attend": 0.19850142013240785 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.005764539166296878, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.13764128251529073, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.16710441607001986, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.04245779938404666, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.07023724634135614, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.051720923806702845, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.11411618300267751, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.19079301720982428, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.22780056599436455, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.20509336751041, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.2534916563112233, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.23360354492760613, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.010530264051684455, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.07370280213290151, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.0128154922623471, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "candidate_frame_written": true, + "environment": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "microcosm-build": "0.1.0", + "microcosm-frame": "0.1.0", + "policyengine-us": "2.2.1", + "python": "3.13.5" + }, + "code_sha256": { + "prepare_us_childcare_attendance.py": "ca4f504a652a9005f692cbd008f430fff0a25bbe32629b75eca80f0be0c7fa51", + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_sibling_validation.py": "abd2a4c88d3c4585e055fae93be3ac00c060405fc6e24bb079b67196aa88e422", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645", + "childcare_sensitivity.py": "a3d370bcaf78b609d81c542dc774b7dab0202ea02f53f80135417320840e06ac", + "nsece_childcare_assessment.py": "537d5d839ce9df4f1772ee887597811990aab5b7f10a863b70fe6630583fa3b7", + "nsece_childcare_pooling.py": "a779a0db7fddbb9e33aa7f8c8f7752b5f4a083309bb7d8f03d6ab72555a439f2", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "childcare.py": "a8502e1fc15457a7f120936a8940fbb51cba3e2299d92ca9cd69b749f6deea2f", + "childcare_attendance_source.json": "c5beeeb09ab330c305e3250bc7db5a5003336d26fa508872211cb18958ab9e97", + "education_assistance_source.py": "e259e3fce64fdcab62953f79a1aa41719f81c64c7c5dc1a63f8b1e493ff5f83a", + "geography_constants.py": "96938b5dd6bba8f10d0f52d10a9cdcbc16ae9506c4424f58e10347998ff02a8a", + "uv.lock": "635accf67e2b33ed9a1af306928079d42c0b105d65e030ea597fdb26cd5902a1" + }, + "candidate_checkpoint_sha256": "31e4a80cc896aeb769ea034a60e8be68c81bed35a52ff52e27ca62d8cb46f40d", + "parent_population_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "production_stage_executed": true, + "candidate_receipts": { + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operations": [ + { + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance", + "age_min": 0, + "age_max": 12 + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "operation": "regular_hours_schedule_bridge", + "nearest_donors": 10 + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + }, + "childcare_attendance_binding": { + "schema_version": 1, + "binding_sha256": "12d583e1e77f9c53ba57b80c5dc16680e073dcd4ca5ba05e4df1c276b0872e99", + "execution_sha256": "c63f973a39fbe658f31b4408d97363fb5e9a684ad4b897c01b0f8b22e85746a7", + "source_people": 166321, + "retained_people": 166321, + "execution": { + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "context": { + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operations": [ + { + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance", + "age_min": 0, + "age_max": 12 + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "operation": "regular_hours_schedule_bridge", + "nearest_donors": 10 + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + } + } + } + } + }, + "native_candidate_written": true, + "native_candidate_sha256": "e7b9f98c592ae34e58e27a1e6affc68e6b4ab82b5f0427eb7c12026fab80c510" +} diff --git a/experiments/us-childcare-attendance/calendar-review-2.2.1-verification.json b/experiments/us-childcare-attendance/calendar-review-2.2.1-verification.json new file mode 100644 index 000000000..3d03500f5 --- /dev/null +++ b/experiments/us-childcare-attendance/calendar-review-2.2.1-verification.json @@ -0,0 +1,39 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "checkpoint_sha256": "31e4a80cc896aeb769ea034a60e8be68c81bed35a52ff52e27ca62d8cb46f40d", + "native_sha256": "e7b9f98c592ae34e58e27a1e6affc68e6b4ab82b5f0427eb7c12026fab80c510", + "source_stage_report_sha256": "7406a90591e610e75c679e05bdaf0ca8f4291c31444647553b21f7215ea0b3e1", + "verification_code_sha256": "3b4cd482f6465325f66d1d8e1b3d680b281073d7dcb6b4bbedc7899e16d27ed4", + "attendance_recipe": { + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09" + }, + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + } + }, + "content_binding_sha256": "12d583e1e77f9c53ba57b80c5dc16680e073dcd4ca5ba05e4df1c276b0872e99", + "engine_version": "2.2.1", + "core_version": "3.32.5", + "people": 166321, + "households": 57240, + "under13_children": 31889, + "verified_native_loaders": [ + "microcosm.build.us_runtime.h5_io.load_legacy_calibrated_us_h5", + "microcosm.build.us_runtime.l0_refit_export.load_us_frame" + ], + "all_original_columns_and_weights_preserved": true, + "all_attendance_values_preserved_on_native_reload": true, + "comparison": "exact values and dtypes, normalizing only pandas string storage backend", + "interpretation": "artifact integrity and preservation only; no statistical certification or publication", + "production_ready": false +} diff --git a/experiments/us-childcare-attendance/calendar-selection-plan.txt b/experiments/us-childcare-attendance/calendar-selection-plan.txt new file mode 100644 index 000000000..7fe68737d --- /dev/null +++ b/experiments/us-childcare-attendance/calendar-selection-plan.txt @@ -0,0 +1,46 @@ +Calendar selection and household composition investigation, 2026-09-16. + +All previously inspected NSECE records are development evidence. This plan +does not create an untouched validation sample or alter the existing screens. + +1. Audit information loss before fitting another model. The source guide + (HH-334) says unreported calendar time can be encoded as parental care, and + calendar-derived summaries do not account for the missing-calendar flag. + Therefore the regular-instrument summaries are not independent observed + outcomes for partial/missing calendars. For complete-but-ambiguous calendars, + retain definite ECE hours/days as lower bounds and allow every unresolved + block to be ECE in upper bounds. For partial calendars, code 0 also remains + unresolved; fully missing calendars have no informative attendance bounds. + Bounds are not imputations, observed zeros, or confidence intervals. + +2. Compare the existing pooled population-moment model with one predetermined + composition-aware empirical model. Keep exact age and whole joint day/hour + schedules. Add under-6 count, school-age (6-12) count, youngest-child age band + (0-2, 3-5, 6-12), and whether a child has a younger sibling. Counts are capped + at three and use the full roster before calendar selection. Pool in order: + age; age + under-13 count; + under-6 count; + school-age count; + + youngest-child age band; + younger-sibling indicator; + parent work; + + income band; + region. Retain strength 10 and the shared population-moment + dependence objective. No new source-only response indicator enters target + predictors, and no smoothing or dependence coefficient is selected by the + evaluation outcomes. + +3. Use the same five whole-household folds and all 15 household plus 18 observed- + child checks. Exclude fitting households from their own donor distributions. + Keep the unresolved-sibling subgroup visible. Report conditional-mean and + random-draw errors, correlation/joint-moment compatibility and donor support. + +4. Use the calendar bounds to test the consequences of the source exclusions, + including whether uncertainty is concentrated among high-attendance families. + Do not promote a completion rule merely because it makes validation pass. + Any additional model must have a separately recorded rationale and evaluation + plan before its results, and must preserve the measured parts of calendars. + +5. Rebuild the current approved-in-code attendance recipe from the original + SHA-pinned parent under PolicyEngine-US 2.2.1. Verify both native loaders, + content/runtime receipts, every original column/weight and all attendance + rows. Rerun all-state attendance comparisons and noncalendar stress scenarios. + A model adoption requires equivalent source/target/bridge integration and a + fresh candidate; diagnostic models never silently replace the build recipe. + Statistical source selection, population qualification, and publication are + separate. No population publication or relaxed release gate is authorized. diff --git a/experiments/us-childcare-attendance/calendar-selection-validation.json b/experiments/us-childcare-attendance/calendar-selection-validation.json new file mode 100644 index 000000000..b53bff8ad --- /dev/null +++ b/experiments/us-childcare-attendance/calendar-selection-validation.json @@ -0,0 +1,1896 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "plan_sha256": "09064740a3e074c83188ff3c0323703639b29aae80cbc627c7e6716ada56cceb", + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "diagnostic_code_sha256": { + "validate_us_childcare_calendar_selection.py": "83905fb60bd6b97bd4338177afce1a329b0bc6463a12c6fb9c27616f982ca60a", + "nsece_childcare_pooling.py": "a779a0db7fddbb9e33aa7f8c8f7752b5f4a083309bb7d8f03d6ab72555a439f2", + "nsece_childcare_sibling_validation.py": "abd2a4c88d3c4585e055fae93be3ac00c060405fc6e24bb079b67196aa88e422" + }, + "production_ready": false, + "interpretation": "fixed model comparison on previously inspected development data; no independent validation claim", + "calendar_bounds": { + "interpretation": "regular questionnaire only; measured calendar classifications conditional on published completeness status; identification bounds, not confidence intervals or completed schedules", + "comparisons": [ + { + "group": "all_regular_instrument", + "children": 8516, + "child_weight": 37015599.704962164, + "mean_hours_lower": 13.752732174958528, + "mean_hours_upper": 19.277785432593692, + "mean_days_lower": 1.8681623591758092, + "mean_days_upper": 2.2700564555072797, + "definitely_in_care_share": 0.4678654605697152, + "possibly_in_care_share": 0.5340113908145044, + "hours_interval_width_le_1_share": 0.8833384698718048 + }, + { + "group": "ambiguous_calendar", + "children": 882, + "child_weight": 3918044.035953992, + "mean_hours_lower": 9.860427216978815, + "mean_hours_upper": 32.59604037159691, + "mean_days_lower": 1.664693482330874, + "mean_days_upper": 4.497773509477583, + "definitely_in_care_share": 0.4411424517865376, + "possibly_in_care_share": 1.0, + "hours_interval_width_le_1_share": 0.11421962556237487 + }, + { + "group": "complete", + "children": 7460, + "child_weight": 32249785.682044942, + "mean_hours_lower": 14.240422572045741, + "mean_hours_upper": 14.240422572045741, + "mean_days_lower": 1.8750709563867467, + "mean_days_upper": 1.8750709563867467, + "definitely_in_care_share": 0.46514845107061753, + "possibly_in_care_share": 0.46514845107061753, + "hours_interval_width_le_1_share": 1.0 + }, + { + "group": "partial_calendar", + "children": 174, + "child_weight": 847769.9869632281, + "mean_hours_lower": 13.189267360358828, + "mean_hours_upper": 149.35123108179786, + "mean_days_lower": 2.5457037310587567, + "mean_days_upper": 7.000000000000001, + "definitely_in_care_share": 0.6947252246391856, + "possibly_in_care_share": 1.0, + "hours_interval_width_le_1_share": 0.0 + } + ] + }, + "pooled_moments": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region" + ], + "fallback_match_columns": [], + "matching_counts": { + "partially_pooled": 7460 + }, + "training_rho": [ + 0.7652583390107988, + 0.7261225249617242, + 0.7209478813856636, + 0.7820313193382383, + 0.81974924157628 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.24236105200931968, + "correlation": 0.09396101556276153 + }, + "days": { + "joint_product": 4.135970938921813, + "correlation": 0.11086235305220805 + }, + "weekly_hours": { + "joint_product": 234.25280025856392, + "correlation": 0.09296236405577511 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3478032040042286, + "correlation": 0.5194576064772242 + }, + "days": { + "joint_product": 6.714922913449467, + "correlation": 0.5919617343470333 + }, + "weekly_hours": { + "joint_product": 426.7255628007384, + "correlation": 0.4820957188644195 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.20749391246494672, + "correlation": 0.07367636621560582 + }, + "days": { + "joint_product": 3.5642905999293837, + "correlation": 0.09515516766062374 + }, + "weekly_hours": { + "joint_product": 216.49341419707955, + "correlation": 0.063874755675981 + } + }, + "coupled": { + "participation": { + "joint_product": 0.31038475496885254, + "correlation": 0.49338255183709395 + }, + "days": { + "joint_product": 6.0717725199533, + "correlation": 0.5677801532872357 + }, + "weekly_hours": { + "joint_product": 437.0722821479231, + "correlation": 0.45599184970931156 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.051031204266368, + "mean_total_weekly_hours": 38.34794143790316, + "sd_total_days": 4.395160013019163, + "sd_total_weekly_hours": 44.20739654038455, + "all_children_attend": 0.06527654820049239 + }, + "coupled": { + "mean_total_days": 5.051031204266368, + "mean_total_weekly_hours": 38.34794143790316, + "sd_total_days": 6.177046168973302, + "sd_total_weekly_hours": 59.820431157004805, + "all_children_attend": 0.20892405597176808 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.01921450534512381, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.012073097681990386, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.04136146849380151, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.13482162586925894, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.1605879769508426, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.03748904845240625, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.006012095626027891, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.1063610218108279, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.1940846238304513, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.2956161341363792, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.12005018174031427, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.15906022067672387, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.07141759048294442, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.0641077309532437, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.023238128101707334, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + }, + "model": { + "name": "partially_pooled_empirical_schedules", + "levels": [ + [ + "age" + ], + [ + "age", + "childcare_household_size" + ], + [ + "age", + "childcare_household_size", + "parent_work_status" + ], + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band" + ], + [ + "age", + "childcare_household_size", + "parent_work_status", + "income_band", + "region" + ] + ], + "composition": false, + "strength": 10.0, + "dependence_objective": "population_moments", + "dependence_fits": [ + { + "rho": 0.7652583390107988, + "unconstrained_rho": 0.7652583390107988, + "households": 1730, + "households_with_unresolved_siblings": 140, + "observed_pairs": 3670, + "outcome_second_moments": [ + 0.4359359926938946, + 8.277205060093346, + 593.7870077076072 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2502298693914833, + 0.27527547268127456, + 0.2850732476726967 + ], + "normalized_mean_shared_increment": [ + 0.30253311517140813, + 0.38487878025611877, + 0.36627648825439335 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.7261225249617242, + "unconstrained_rho": 0.7261225249617242, + "households": 1697, + "households_with_unresolved_siblings": 123, + "observed_pairs": 3594, + "outcome_second_moments": [ + 0.44126287158160343, + 8.359355045936002, + 694.2809842648821 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.22454696976548358, + 0.25665784026502875, + 0.3085026964880856 + ], + "normalized_mean_shared_increment": [ + 0.3121975302394453, + 0.3956058631862616, + 0.37836062350370525 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.7209478813856636, + "unconstrained_rho": 0.7209478813856636, + "households": 1633, + "households_with_unresolved_siblings": 135, + "observed_pairs": 3453, + "outcome_second_moments": [ + 0.4338366370776943, + 8.256340131249516, + 694.144636825422 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.22762977893389097, + 0.2491739115147974, + 0.31614054777338935 + ], + "normalized_mean_shared_increment": [ + 0.3218355084453624, + 0.40226719431104246, + 0.37196853323063156 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.7820313193382383, + "unconstrained_rho": 0.7820313193382383, + "households": 1668, + "households_with_unresolved_siblings": 125, + "observed_pairs": 3499, + "outcome_second_moments": [ + 0.4428268423383291, + 8.341086892139936, + 679.4142064233605 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.24679819920578627, + 0.27437897292653474, + 0.337362053609579 + ], + "normalized_mean_shared_increment": [ + 0.3146404967452197, + 0.40232577509104867, + 0.3772940863523165 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.81974924157628, + "unconstrained_rho": 0.81974924157628, + "households": 1696, + "households_with_unresolved_siblings": 137, + "observed_pairs": 3584, + "outcome_second_moments": [ + 0.4413586153564347, + 8.485578051981562, + 676.0109573424492 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2543497335812991, + 0.28684691450840843, + 0.35816569978000246 + ], + "normalized_mean_shared_increment": [ + 0.3106084542826501, + 0.4028661500770573, + 0.38060896199156885 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + } + ] + }, + "matching_count_universe": "every held-out child with an observed complete calendar", + "observed_child_validation": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.46964890773471457, + "days": 1.8870313262922465, + "weekly_hours": 14.121321880052811 + }, + "conditional_mean_squared_error": { + "participation": 0.21786002566014795, + "days": 4.634484500691751, + "weekly_hours": 482.79806934933634 + }, + "expected_draw_squared_error": { + "participation": 0.4189867368126556, + "days": 8.871248441333941, + "weekly_hours": 905.9935560782225 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5309528476583814, + "days": 2.1450855821675643, + "weekly_hours": 16.7286558904403 + }, + "conditional_mean_squared_error": { + "participation": 0.21998157968908702, + "days": 4.8634925946342795, + "weekly_hours": 551.1632871152738 + }, + "expected_draw_squared_error": { + "participation": 0.4281854166170082, + "days": 9.372662082607494, + "weekly_hours": 1034.4761146276157 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.48651284376374204, + "days": 1.9627422616863737, + "weekly_hours": 14.1440528078623 + }, + "conditional_mean_squared_error": { + "participation": 0.21802706174990996, + "days": 4.583635321783541, + "weekly_hours": 414.2968731100742 + }, + "expected_draw_squared_error": { + "participation": 0.4176700262900522, + "days": 8.771188666124276, + "weekly_hours": 779.6025285290256 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.39657324091775625, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.3918312548530712, + "days": 1.5531811863063212, + "weekly_hours": 11.73540600582258 + }, + "conditional_mean_squared_error": { + "participation": 0.2157210644872454, + "days": 4.495213713500396, + "weekly_hours": 512.1441841789803 + }, + "expected_draw_squared_error": { + "participation": 0.41242723371549633, + "days": 8.551318544556374, + "weekly_hours": 958.0235058267347 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.91535441, + "observed": { + "participation": 0.44408939246351814, + "days": 1.7729192348218488, + "weekly_hours": 13.20243633175764 + }, + "expected": { + "participation": 0.46990822362498935, + "days": 1.8840103414261442, + "weekly_hours": 14.119885124808388 + }, + "conditional_mean_squared_error": { + "participation": 0.21582355854647822, + "days": 4.473903862652206, + "weekly_hours": 447.4809381900267 + }, + "expected_draw_squared_error": { + "participation": 0.4175485380163647, + "days": 8.708831987458572, + "weekly_hours": 872.7199874173884 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.829753088509108 + }, + "expected": { + "participation": 0.46725324420518616, + "days": 1.914940388322065, + "weekly_hours": 14.13459519753524 + }, + "conditional_mean_squared_error": { + "participation": 0.23667372037103993, + "days": 6.117992424918431, + "weekly_hours": 809.0718005751868 + }, + "expected_draw_squared_error": { + "participation": 0.4322733904027592, + "days": 10.371716363808677, + "weekly_hours": 1213.3880426008857 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.0045004566640959864, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.006378622560794653, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.008363564451150505, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.006741956249488146, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.01207344476879113, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.010816598381105233, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.009926610797840407, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.02168017950925578, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.014518982739467643, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0047419860646850664, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.02501084894127077, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.0400086844711946, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.025818831161471212, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.06265999286507735, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.06949087047243553, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.19244718213343287, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.3206514545518074, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.4068509587557979, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "all_observed_sibling_pairs": { + "household_weight": 8945945.325594446, + "observed": { + "participation": { + "joint_product": 0.3302177486685269, + "correlation": 0.569690244749424 + }, + "days": { + "joint_product": 6.027208927705104, + "correlation": 0.5724522456066773 + }, + "weekly_hours": { + "joint_product": 426.22614842124585, + "correlation": 0.5408408373849536 + } + }, + "independent": { + "participation": { + "joint_product": 0.2260919090368316, + "correlation": 0.09520635050450324 + }, + "days": { + "joint_product": 3.8285744975466867, + "correlation": 0.10968106727006208 + }, + "weekly_hours": { + "joint_product": 212.90904376258283, + "correlation": 0.08730651835284241 + } + }, + "coupled": { + "participation": { + "joint_product": 0.33074016165677533, + "correlation": 0.5208339015307502 + }, + "days": { + "joint_product": 6.372410806864982, + "correlation": 0.5918345128361356 + }, + "weekly_hours": { + "joint_product": 401.34477075365476, + "correlation": 0.4737561541826404 + } + }, + "observed_pairs": 4450, + "households": 2106, + "interpretation": "all observed pairs including households with unresolved other siblings; household weight divided among observed pairs" + }, + "dependence_only_screen_compatibility": { + "comparisons": [ + { + "population": "youngest_pairs", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.5416889767723654, + "predicted_product_of_marginal_standard_deviations": 5.360538954730504, + "joint_product_required_by_correlation_screen": [ + 6.114150707547841, + 7.186258498493942 + ], + "joint_product_allowed_by_product_screen": [ + 4.733729256035931, + 7.100593884053898 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 6.114150707547841, + 7.100593884053898 + ] + }, + { + "population": "youngest_pairs", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 188.27184591178198, + "predicted_product_of_marginal_standard_deviations": 494.6190301184091, + "joint_product_required_by_correlation_screen": [ + 397.7218292195747, + 496.6456352432565 + ], + "joint_product_allowed_by_product_screen": [ + 294.14439665098314, + 441.21659497647465 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 397.7218292195747, + 441.21659497647465 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.0594508860714296, + "predicted_product_of_marginal_standard_deviations": 5.305436649100272, + "joint_product_required_by_correlation_screen": [ + 5.509332062571049, + 6.570419392391104 + ], + "joint_product_allowed_by_product_screen": [ + 4.067900983751674, + 6.101851475627511 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 5.509332062571049, + 6.101851475627511 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 180.56174604989343, + "predicted_product_of_marginal_standard_deviations": 562.5331598833434, + "joint_product_required_by_correlation_screen": [ + 440.650567847255, + 553.1571998239236 + ], + "joint_product_allowed_by_product_screen": [ + 269.8776408426022, + 404.8164612639033 + ], + "screen_intervals_overlap": false, + "overlap_interval": null + } + ], + "interpretation": "disjoint screen intervals rule out a dependence-only fix for these conditional marginals; overlapping intervals do not establish that a feasible joint distribution exists" + } + }, + "composition": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling", + "parent_work_status", + "income_band", + "region" + ], + "fallback_match_columns": [], + "matching_counts": { + "partially_pooled": 7460 + }, + "training_rho": [ + 0.8399640846621534, + 0.8570423272969275, + 0.8230102157132589, + 0.864578324659506, + 0.9166961809991262 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.23403263223181478, + "correlation": 0.05505364256311636 + }, + "days": { + "joint_product": 3.793349125556499, + "correlation": 0.05205827852433274 + }, + "weekly_hours": { + "joint_product": 204.3674792287307, + "correlation": 0.052403482951996 + } + }, + "coupled": { + "participation": { + "joint_product": 0.35484511473052854, + "correlation": 0.5445752375628808 + }, + "days": { + "joint_product": 6.718882876240173, + "correlation": 0.6060563801476498 + }, + "weekly_hours": { + "joint_product": 407.28869084882973, + "correlation": 0.48750091812507396 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.18622994721190744, + "correlation": 0.004424326621928326 + }, + "days": { + "joint_product": 2.9357117242328004, + "correlation": 0.003213461024514607 + }, + "weekly_hours": { + "joint_product": 160.51024640001995, + "correlation": 0.0011368384394220272 + } + }, + "coupled": { + "participation": { + "joint_product": 0.29900866121222525, + "correlation": 0.46870525036610444 + }, + "days": { + "joint_product": 5.682552831905449, + "correlation": 0.540022542299082 + }, + "weekly_hours": { + "joint_product": 371.00824393473454, + "correlation": 0.455014242859092 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.092979651989213, + "mean_total_weekly_hours": 37.72006977012014, + "sd_total_days": 4.138811595986753, + "sd_total_weekly_hours": 41.22796200054727, + "all_children_attend": 0.052578178673632084 + }, + "coupled": { + "mean_total_days": 5.092979651989213, + "mean_total_weekly_hours": 37.72006977012014, + "sd_total_days": 6.181228072584479, + "sd_total_weekly_hours": 58.28009064320519, + "all_children_attend": 0.20401682495167728 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.02625641607142376, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.026167743482606975, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.03595626923314704, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.1354908593765461, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.10772449310220358, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.026112954695778956, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.021745515362125878, + "relative": false, + "limit": 0.1, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.10733862866104749, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.11754004920043884, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.09978208724927644, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.12935211724528825, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.14008290282002275, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.07214294771165103, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.03670758994234549, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.018330897081616532, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + }, + "model": { + "name": "partially_pooled_empirical_schedules", + "levels": [ + [ + "age" + ], + [ + "age", + "childcare_household_size" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling", + "parent_work_status" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling", + "parent_work_status", + "income_band" + ], + [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling", + "parent_work_status", + "income_band", + "region" + ] + ], + "composition": true, + "strength": 10.0, + "dependence_objective": "population_moments", + "dependence_fits": [ + { + "rho": 0.8399640846621534, + "unconstrained_rho": 0.8399640846621534, + "households": 1730, + "households_with_unresolved_siblings": 140, + "observed_pairs": 3670, + "outcome_second_moments": [ + 0.4359359926938946, + 8.277205060093346, + 593.7870077076072 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2628889402077603, + 0.30752410801290964, + 0.3162598566632183 + ], + "normalized_mean_shared_increment": [ + 0.30573202616046746, + 0.3859771076106248, + 0.36143383682784197 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.8570423272969275, + "unconstrained_rho": 0.8570423272969275, + "households": 1697, + "households_with_unresolved_siblings": 123, + "observed_pairs": 3594, + "outcome_second_moments": [ + 0.44126287158160343, + 8.359355045936002, + 694.2809842648821 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2443600196619892, + 0.2990893124631417, + 0.3469623228273086 + ], + "normalized_mean_shared_increment": [ + 0.3093672228441525, + 0.39089237144982414, + 0.3331400290247654 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.8230102157132589, + "unconstrained_rho": 0.8230102157132589, + "households": 1633, + "households_with_unresolved_siblings": 135, + "observed_pairs": 3453, + "outcome_second_moments": [ + 0.4338366370776943, + 8.256340131249516, + 694.144636825422 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.24816322664921883, + 0.2859806861379085, + 0.3421789090009365 + ], + "normalized_mean_shared_increment": [ + 0.32207809778668695, + 0.3942246598593399, + 0.3426789060896911 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.864578324659506, + "unconstrained_rho": 0.864578324659506, + "households": 1668, + "households_with_unresolved_siblings": 125, + "observed_pairs": 3499, + "outcome_second_moments": [ + 0.4428268423383291, + 8.341086892139936, + 679.4142064233605 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.25329497627486214, + 0.3037274504890692, + 0.36190503905052646 + ], + "normalized_mean_shared_increment": [ + 0.3108759554369621, + 0.39736383237268086, + 0.35048479033181906 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + }, + { + "rho": 0.9166961809991262, + "unconstrained_rho": 0.9166961809991262, + "households": 1696, + "households_with_unresolved_siblings": 137, + "observed_pairs": 3584, + "outcome_second_moments": [ + 0.4413586153564347, + 8.485578051981562, + 676.0109573424492 + ], + "objective": "population_moments", + "normalized_mean_residual": [ + 0.2716286888941078, + 0.32001227998102094, + 0.3876781485857021 + ], + "normalized_mean_shared_increment": [ + 0.3135365037544654, + 0.4022432617843371, + 0.3453688379556845 + ], + "donor_household_overlap": 0, + "estimation": "least squares on participation/days/weekly-hours cross-products under the named objective; all observed pairs; household weight divided among pairs; leave whole fitting household out of donors" + } + ] + }, + "matching_count_universe": "every held-out child with an observed complete calendar", + "observed_child_validation": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.47072573788331684, + "days": 1.891415566726541, + "weekly_hours": 14.051047147885557 + }, + "conditional_mean_squared_error": { + "participation": 0.22658158908250814, + "days": 4.838377344868154, + "weekly_hours": 502.89523404077755 + }, + "expected_draw_squared_error": { + "participation": 0.4301190485183399, + "days": 9.155938510049051, + "weekly_hours": 919.6053921259771 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5334523349716616, + "days": 2.157243247532589, + "weekly_hours": 16.87219330898405 + }, + "conditional_mean_squared_error": { + "participation": 0.2203944172588559, + "days": 4.875138146962889, + "weekly_hours": 552.334405111881 + }, + "expected_draw_squared_error": { + "participation": 0.42868789798870033, + "days": 9.398241596110811, + "weekly_hours": 1039.203894337865 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.48969985296599344, + "days": 1.9755769244726475, + "weekly_hours": 14.151729788046943 + }, + "conditional_mean_squared_error": { + "participation": 0.2264818868005256, + "days": 4.746127518844021, + "weekly_hours": 423.93181581930173 + }, + "expected_draw_squared_error": { + "participation": 0.4310797875791831, + "days": 9.081249304141137, + "weekly_hours": 802.3902348660534 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.39657324091775625, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.3888160055444834, + "days": 1.5393024280784862, + "weekly_hours": 11.368272677914653 + }, + "conditional_mean_squared_error": { + "participation": 0.23230420614490324, + "days": 4.927865379531953, + "weekly_hours": 563.2564297064381 + }, + "expected_draw_squared_error": { + "participation": 0.4301341968231166, + "days": 9.036365363174667, + "weekly_hours": 967.4571795917512 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.91535441, + "observed": { + "participation": 0.44408939246351814, + "days": 1.7729192348218488, + "weekly_hours": 13.20243633175764 + }, + "expected": { + "participation": 0.4730372440960363, + "days": 1.8975754143009322, + "weekly_hours": 14.097344593444705 + }, + "conditional_mean_squared_error": { + "participation": 0.2229085673024625, + "days": 4.649026562188718, + "weekly_hours": 463.3225631391084 + }, + "expected_draw_squared_error": { + "participation": 0.42716654608125787, + "days": 8.974736797922452, + "weekly_hours": 882.6176744431627 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.829753088509108 + }, + "expected": { + "participation": 0.44937112237467763, + "days": 1.8345084401875262, + "weekly_hours": 13.623332896672485 + }, + "conditional_mean_squared_error": { + "participation": 0.26051442795448626, + "days": 6.5876753299361495, + "weekly_hours": 868.4833372029435 + }, + "expected_draw_squared_error": { + "participation": 0.4573954425791977, + "days": 10.829952134014457, + "weekly_hours": 1261.3126673704833 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.005577286812698257, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.00871679564131799, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.013298441335015258, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.009241443562768348, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.017809556357352218, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.002329076558396374, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.013113620000091808, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.028361097750734178, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.015069634114518045, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0077572353732728305, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.03372305767880146, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.07004128890413303, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.028947851632518173, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.0703112567288546, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.06778356957756485, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.2103293039639414, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.3491856728000537, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.4283057467665596, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "all_observed_sibling_pairs": { + "household_weight": 8945945.325594446, + "observed": { + "participation": { + "joint_product": 0.3302177486685269, + "correlation": 0.569690244749424 + }, + "days": { + "joint_product": 6.027208927705104, + "correlation": 0.5724522456066773 + }, + "weekly_hours": { + "joint_product": 426.22614842124585, + "correlation": 0.5408408373849536 + } + }, + "independent": { + "participation": { + "joint_product": 0.21912452735602767, + "correlation": 0.06104800089898952 + }, + "days": { + "joint_product": 3.547131863081251, + "correlation": 0.05993325215301244 + }, + "weekly_hours": { + "joint_product": 191.1715812535131, + "correlation": 0.058509149210166214 + } + }, + "coupled": { + "participation": { + "joint_product": 0.33817967669080706, + "correlation": 0.5465533102310314 + }, + "days": { + "joint_product": 6.401711376096358, + "correlation": 0.6089880012372204 + }, + "weekly_hours": { + "joint_product": 389.86012743614157, + "correlation": 0.48451497075234107 + } + }, + "observed_pairs": 4450, + "households": 2106, + "interpretation": "all observed pairs including households with unresolved other siblings; household weight divided among observed pairs" + }, + "dependence_only_screen_compatibility": { + "comparisons": [ + { + "population": "youngest_pairs", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 3.518441593600768, + "predicted_product_of_marginal_standard_deviations": 5.280764937842418, + "joint_product_required_by_correlation_screen": [ + 6.052620680170525, + 7.108773667739009 + ], + "joint_product_allowed_by_product_screen": [ + 4.733729256035931, + 7.100593884053898 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 6.052620680170525, + 7.100593884053898 + ] + }, + { + "population": "youngest_pairs", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 179.92748626599993, + "predicted_product_of_marginal_standard_deviations": 466.3810797675209, + "joint_product_required_by_correlation_screen": [ + 377.41990654144445, + 470.69612249494855 + ], + "joint_product_allowed_by_product_screen": [ + 294.14439665098314, + 441.21659497647465 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 377.41990654144445, + 441.21659497647465 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "days", + "identified": true, + "predicted_product_of_marginal_means": 2.9192685097866464, + "predicted_product_of_marginal_standard_deviations": 5.116979580804991, + "joint_product_required_by_correlation_screen": [ + 5.282126231907029, + 6.305522148068027 + ], + "joint_product_allowed_by_product_screen": [ + 4.067900983751674, + 6.101851475627511 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 5.282126231907029, + 6.101851475627511 + ] + }, + { + "population": "youngest_pairs_in_3plus_households", + "metric": "weekly_hours", + "identified": true, + "predicted_product_of_marginal_means": 159.98300663728094, + "predicted_product_of_marginal_standard_deviations": 463.7772127120063, + "joint_product_required_by_correlation_screen": [ + 374.4117326802836, + 467.1671752226848 + ], + "joint_product_allowed_by_product_screen": [ + 269.8776408426022, + 404.8164612639033 + ], + "screen_intervals_overlap": true, + "overlap_interval": [ + 374.4117326802836, + 404.8164612639033 + ] + } + ], + "interpretation": "disjoint screen intervals rule out a dependence-only fix for these conditional marginals; overlapping intervals do not establish that a feasible joint distribution exists" + } + } +} diff --git a/experiments/us-childcare-attendance/qrf-calendar-plan.txt b/experiments/us-childcare-attendance/qrf-calendar-plan.txt new file mode 100644 index 000000000..3d20de858 --- /dev/null +++ b/experiments/us-childcare-attendance/qrf-calendar-plan.txt @@ -0,0 +1,42 @@ +Canonical conditional-model diagnostic, specified after the composition result +and before this model's results, 2026-09-16. + +The composition-cell model improves the original joint-screen count from two +failures to one but worsens the unresolved-sibling hours prediction. It is not +adopted. Test whether rigid matching cells, rather than insufficient observed +predictors alone, explain that marginal error. + +Use microcosm.fit's canonical regime-gated weighted-bootstrap QRF (100 trees, +seed 915, existing defaults). Predictors: the full-roster composition features +from calendar-selection-plan.txt, region, parent work, income band, measured +household income, number of household members, and number of resident parents +of under-13 children. NSECE HH4_HHCOMP_MEMBERS counts all residents (HH-118); +HH4_HHCOMP_NUMPARENTS is household-wide, not each child's parent count (HH-121). +Corresponding ASEC fields can be derived from household membership and the +existing resident-parent pointers. No calendar-response/status, attendance, +provider usage, or source-only feature enters the predictors. Weekly NSECE +cost after subsidies is not treated as interchangeable with annual ASEC care +expenses. + +Fit one scalar joint schedule code: 25 * attended days + average daily hours. +This orders schedules by days then daily hours, just as the existing donor CDF; +zero means observed nonattendance. Decode positive QRF draws to the nearest +observed positive schedule code among training donors of the recipient's exact +age (ties choose the lower code). This is an ordinal distributional-matching +model, not an observed quantity or a claim that days and hours are fungible. +All output schedules therefore remain observed joint pairs; no fractional or +independently combined day/hour schedule is invented. Record decoder movement. + +Evaluate all 7,460 observed children using the same five whole-household folds. +Fit all QRF parameters and age-specific decoder catalogs solely on training +households. Use a fixed 128-point two-dimensional Sobol grid for gate/quantile +integration, and retain the empirical resulting CDF. The grid is fixed before +results, not a selected simulation seed. Report the same 18 observed-child +screens, conditional-mean MSE and expected random-draw error. These data have +already informed development; no independent validation claim is possible. + +This is a marginal-model diagnostic first. Only a promising result proceeds to +cross-fitted dependence estimation, joint household checks, higher-resolution +integration stability, source/target/bridge integration and a fresh population +candidate. Do not adopt it on child-average performance alone. Calendar +nonresponse and noncalendar duration assumptions remain unidentified. diff --git a/experiments/us-childcare-attendance/qrf-calendar-validation.json b/experiments/us-childcare-attendance/qrf-calendar-validation.json new file mode 100644 index 000000000..ded101e07 --- /dev/null +++ b/experiments/us-childcare-attendance/qrf-calendar-validation.json @@ -0,0 +1,417 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "plan_sha256": "8f3ea047c8d87a27faf5dbb0f7ce719f5aab9a1a8508fb3b6602d0cbbc4d4577", + "diagnostic_code_sha256": { + "validate_us_childcare_qrf.py": "78fd063407aaa24a1565ddab8b8b527c4cfa8a04f6a72b8b750658e511ede8d7", + "nsece_childcare_qrf.py": "cd18d77cce2199246b22407f4dc7f19f35fff25d94af4ce128c94b86188f838a" + }, + "model": { + "name": "canonical_weighted_QRF_joint_schedule_decoder", + "predictors": [ + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling", + "parent_work_status", + "income_band", + "region", + "household_income", + "household_members", + "resident_parent_count" + ], + "trees": 100, + "seed": 915, + "integration_points": 128, + "mean_positive_decoder_code_movement": 0.2967616315205634, + "max_positive_decoder_code_movement": 12.0 + }, + "splits": [ + { + "fold": 0, + "training_households": 5100, + "evaluation_households": 857, + "household_overlap": 0, + "evaluated_children": 1402, + "profiles": 1361 + }, + { + "fold": 1, + "training_households": 5108, + "evaluation_households": 864, + "household_overlap": 0, + "evaluated_children": 1461, + "profiles": 1422 + }, + { + "fold": 2, + "training_households": 5055, + "evaluation_households": 928, + "household_overlap": 0, + "evaluated_children": 1620, + "profiles": 1578 + }, + { + "fold": 3, + "training_households": 5071, + "evaluation_households": 874, + "household_overlap": 0, + "evaluated_children": 1527, + "profiles": 1482 + }, + { + "fold": 4, + "training_households": 5142, + "evaluation_households": 849, + "household_overlap": 0, + "evaluated_children": 1450, + "profiles": 1410 + } + ], + "observed_child_validation": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.4626963402375239, + "days": 1.8782143152899593, + "weekly_hours": 14.28498839270566 + }, + "conditional_mean_squared_error": { + "participation": 0.22193735839098713, + "days": 4.738177957705935, + "weekly_hours": 495.36993857227833 + }, + "expected_draw_squared_error": { + "participation": 0.3980913088338638, + "days": 8.493289037594954, + "weekly_hours": 884.5670742156525 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5169591695664993, + "days": 2.1040041234632554, + "weekly_hours": 16.180318254505547 + }, + "conditional_mean_squared_error": { + "participation": 0.21309207916173845, + "days": 4.80186267546308, + "weekly_hours": 547.7558761783448 + }, + "expected_draw_squared_error": { + "participation": 0.3939817087645831, + "days": 8.676322304138447, + "weekly_hours": 939.0868577063078 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.47437966009242294, + "days": 1.9431615330846461, + "weekly_hours": 14.443491705680222 + }, + "conditional_mean_squared_error": { + "participation": 0.2237912246436724, + "days": 4.589094013107792, + "weekly_hours": 408.2440342383202 + }, + "expected_draw_squared_error": { + "participation": 0.39728485474399067, + "days": 8.251821584734268, + "weekly_hours": 751.137626070225 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.3965732409177563, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.39813090829428704, + "days": 1.587831290116023, + "weekly_hours": 12.361766263154577 + }, + "conditional_mean_squared_error": { + "participation": 0.22746310337909134, + "days": 4.878935323151912, + "weekly_hours": 563.9256416456769 + }, + "expected_draw_squared_error": { + "participation": 0.40287692057375285, + "days": 8.64909563220651, + "weekly_hours": 1012.7828745314389 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.915354412, + "observed": { + "participation": 0.4440893924635181, + "days": 1.7729192348218485, + "weekly_hours": 13.202436331757639 + }, + "expected": { + "participation": 0.4611924940358734, + "days": 1.8679016105224053, + "weekly_hours": 14.20249149230845 + }, + "conditional_mean_squared_error": { + "participation": 0.21836908152439619, + "days": 4.576891305578152, + "weekly_hours": 458.29836605715 + }, + "expected_draw_squared_error": { + "participation": 0.39552745711675996, + "days": 8.329736490182508, + "weekly_hours": 844.8891652102076 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.8297530885091 + }, + "expected": { + "participation": 0.4765894707182824, + "days": 1.9734871914576904, + "weekly_hours": 15.047127632887687 + }, + "conditional_mean_squared_error": { + "participation": 0.25490252201250563, + "days": 6.22820832142038, + "weekly_hours": 837.8518981731377 + }, + "expected_draw_squared_error": { + "participation": 0.42177719266956654, + "days": 10.004252644889078, + "weekly_hours": 1251.127408916974 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.0024521108330947117, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.0016763946412296814, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.0031295293685603054, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.007251721842393866, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.007309210064487643, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.04324039210977919, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.002206572873478696, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.011487683681736041, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.035997016661823936, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0015576673765307358, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.003259700011982134, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.011229458217074167, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.01710310157235534, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.053574000346440526, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.07574777377605987, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.18311095562033664, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.29988125940976446, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.3685571320441615, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "production_ready": false, + "interpretation": "marginal development diagnostic only; no dependence fit, no source-selection correction, no population integration or independent validation" +} diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py index 9e7f6c4b2..97413345f 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py @@ -188,6 +188,15 @@ def derive_nsece_childcare( default="complete", ) complete = reason == "complete" + # HH-334: unreported time can be encoded as assumed parental care (0). + # For partial calendars those zeros are not observed nonattendance. + # Missing calendars contribute no informative bound, including when + # derived summaries misleadingly describe a parental-care-only week. + definite = ece & (status != 0)[:, None] + unresolved = ( + ~known | ((status == 1)[:, None] & (values == 0)) | (status == 0)[:, None] + ) + possible = definite | unresolved summary_hours = rows[ [f"HHC4_NPC_HRSWEEK_TOC{kind}_{child}" for kind in range(1, 10)] ].to_numpy(dtype=float) @@ -225,6 +234,16 @@ def derive_nsece_childcare( complete, weekly_hours - regular_hours, np.nan ), "attendance_status": reason, + "calendar_status_code": status.astype(int), + "calendar_unknown_hours": unresolved.sum(axis=1) / 4, + "calendar_ece_hours_lower": definite.sum(axis=1) / 4, + "calendar_ece_hours_upper": possible.sum(axis=1) / 4, + "calendar_ece_days_lower": definite.reshape(-1, 7, 96) + .any(axis=2) + .sum(axis=1), + "calendar_ece_days_upper": possible.reshape(-1, 7, 96) + .any(axis=2) + .sum(axis=1), "child_weight": weight.to_numpy(), "household_weight": rows.HH4_METH_WEIGHT.to_numpy(), "ece_provider_count": np.where(complete, provider_count, np.nan), @@ -252,6 +271,12 @@ def derive_nsece_childcare( "measurement": "union_of_ECE_calendar_blocks_excluding_K8", "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", "national_representativeness_validated": False, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days", + }, }, ) diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py index f502e147d..c1593cc67 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_pooling.py @@ -29,6 +29,21 @@ for n in range(1, len(POOLED_CHILDCARE_MATCH_COLUMNS) + 1) ) POOLED_CHILDCARE_STRENGTH = 10.0 +COMPOSITION_CHILDCARE_MATCH_COLUMNS = ( + "age", + "childcare_household_size", + "childcare_under6_count", + "childcare_schoolage_count", + "childcare_youngest_age_band", + "childcare_has_younger_sibling", + "parent_work_status", + "income_band", + "region", +) +COMPOSITION_CHILDCARE_LEVELS = tuple( + COMPOSITION_CHILDCARE_MATCH_COLUMNS[:n] + for n in range(1, len(COMPOSITION_CHILDCARE_MATCH_COLUMNS) + 1) +) def with_childcare_household_size(children): @@ -44,6 +59,22 @@ def with_childcare_household_size(children): .transform("sum") .clip(upper=3) ) + for column, included in ( + ("childcare_under6_count", result.age.between(0, 5)), + ("childcare_schoolage_count", result.age.between(6, 12)), + ): + result[column] = ( + included.groupby(result.source_household_id).transform("sum").clip(upper=3) + ) + youngest = ( + result.age.where(result.age.between(0, 12)) + .groupby(result.source_household_id) + .transform("min") + ) + result["childcare_youngest_age_band"] = np.select( + [youngest < 3, youngest < 6], [0, 1], default=2 + ) + result["childcare_has_younger_sibling"] = (result.age > youngest).astype(int) return result @@ -54,12 +85,18 @@ class PooledScheduleDonors: Multiplying all survey weights by a common factor cannot change predictions. """ - def __init__(self, children, *, strength=POOLED_CHILDCARE_STRENGTH): + def __init__( + self, children, *, strength=POOLED_CHILDCARE_STRENGTH, composition=False + ): if not np.isfinite(strength) or strength <= 0: raise ValueError("Pooling strength must be finite and positive.") self.strength = strength + self.levels = ( + COMPOSITION_CHILDCARE_LEVELS if composition else POOLED_CHILDCARE_LEVELS + ) + self.match_columns = self.levels[-1] required = [ - *POOLED_CHILDCARE_MATCH_COLUMNS, + *self.match_columns, "source_household_id", "donor_id", "child_weight", @@ -79,9 +116,7 @@ def __init__(self, children, *, strength=POOLED_CHILDCARE_STRENGTH): raise ValueError("Pooled childcare donor IDs must be unique.") _ids(self.pool, "source_household_id", unique=False) _ids(self.pool, "donor_id", unique=True) - predictors = self.pool.reindex(columns=POOLED_CHILDCARE_MATCH_COLUMNS).to_numpy( - dtype=float - ) + predictors = self.pool.reindex(columns=self.match_columns).to_numpy(dtype=float) if ( not np.isfinite(predictors).all() or (predictors % 1 != 0).any() @@ -104,14 +139,13 @@ def __init__(self, children, *, strength=POOLED_CHILDCARE_STRENGTH): self.weights = self.pool.child_weight.to_numpy(dtype=float) self.households = self.pool.source_household_id.to_numpy() self.groups = [ - self.pool.groupby(list(level), sort=False).indices - for level in POOLED_CHILDCARE_LEVELS + self.pool.groupby(list(level), sort=False).indices for level in self.levels ] self.cache = {} def distribution(self, child, *, exclude_household=None): """Return care-sorted values, cumulative probabilities and row masses.""" - key = tuple(child.reindex(POOLED_CHILDCARE_MATCH_COLUMNS)) + key = tuple(child.reindex(self.match_columns)) if not np.isfinite(np.asarray(key, dtype=float)).all(): raise ValueError("Pooled target matching fields must be complete.") if exclude_household is None and key in self.cache: @@ -123,9 +157,7 @@ def distribution(self, child, *, exclude_household=None): raise ValueError("No exact-age donor household remains after exclusion.") probabilities = self.weights[age_indices].copy() probabilities /= probabilities.sum() - for level, groups in zip( - POOLED_CHILDCARE_LEVELS[1:], self.groups[1:], strict=True - ): + for level, groups in zip(self.levels[1:], self.groups[1:], strict=True): indices = np.asarray(groups.get(key[: len(level)], []), dtype=int) if exclude_household is not None: indices = indices[self.households[indices] != exclude_household] @@ -151,7 +183,11 @@ def distribution(self, child, *, exclude_household=None): def fit_pooled_sibling_dependence( - children, *, strength=POOLED_CHILDCARE_STRENGTH, objective="pair_squared_error" + children, + *, + strength=POOLED_CHILDCARE_STRENGTH, + objective="pair_squared_error", + composition=False, ): """Fit all measured pair cross-products with whole-household donor exclusion.""" from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( @@ -160,7 +196,7 @@ def fit_pooled_sibling_dependence( if objective not in ("pair_squared_error", "population_moments"): raise ValueError("Unknown pooled dependence objective.") - model = PooledScheduleDonors(children, strength=strength) + model = PooledScheduleDonors(children, strength=strength, composition=composition) records = [] households = 0 incomplete_households = 0 diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py new file mode 100644 index 000000000..f152697a0 --- /dev/null +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py @@ -0,0 +1,150 @@ +"""Diagnostic canonical-QRF matching of observed, exact-age joint schedules. + +This experiment has no production integration or calendar nonresponse correction. +An ordinal schedule code is decoded to an observed day/hour pair; it is not a +new attendance input. All catalog construction and fits use training data only. +""" + +from __future__ import annotations + +import numpy as np +from scipy.stats import qmc + +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + _ids, + _validate_attendance, +) +from microcosm.build.us_runtime.nsece_childcare_pooling import ( + COMPOSITION_CHILDCARE_MATCH_COLUMNS, +) +from microcosm.fit import fit + +QRF_CHILDCARE_PREDICTORS = ( + *COMPOSITION_CHILDCARE_MATCH_COLUMNS, + "household_income", + "household_members", + "resident_parent_count", +) +QRF_CHILDCARE_SCHEDULE_CODE = "childcare_schedule_code" +QRF_CHILDCARE_DAY_MULTIPLIER = 25.0 # Daily hours are bounded above by 24. +QRF_CHILDCARE_GRID_POWER = 7 # Fixed 128-point two-dimensional Sobol integration. + + +class QRFChildcareSchedules: + """Canonical weighted QRF with an age-specific empirical schedule decoder.""" + + def __init__(self, children, *, seed=915, n_estimators=100): + if children.attendance_status.eq("summary_bridge").any(): + raise ValueError("QRF diagnostics require original measured calendars.") + self.training_households = frozenset(children.source_household_id) + pool = ( + children.loc[ + children.age.between(0, 12) & children.attendance_status.eq("complete") + ] + .sort_values("donor_id") + .copy() + ) + if pool.empty: + raise ValueError("QRF attendance requires observed training calendars.") + _ids(pool, "source_household_id", unique=False) + _ids(pool, "donor_id", unique=True) + _validate_attendance(pool, complete=True) + if not np.isfinite( + pool[list(QRF_CHILDCARE_PREDICTORS)].to_numpy(dtype=float) + ).all(): + raise ValueError("QRF attendance predictors must be finite.") + weights = pool.child_weight.to_numpy(dtype=float) + if not np.isfinite(weights).all() or (weights <= 0).any(): + raise ValueError("QRF attendance needs positive finite design weights.") + _, days, hours = US_CHILDCARE_ATTENDANCE_COLUMNS + pool[QRF_CHILDCARE_SCHEDULE_CODE] = ( + QRF_CHILDCARE_DAY_MULTIPLIER * pool[days] + pool[hours] + ) + self.catalogs = {} + self.ages = frozenset(pool.age) + for age, group in pool.loc[pool[days] > 0].groupby("age"): + group = group.sort_values(QRF_CHILDCARE_SCHEDULE_CODE).drop_duplicates( + QRF_CHILDCARE_SCHEDULE_CODE + ) + self.catalogs[age] = ( + group[QRF_CHILDCARE_SCHEDULE_CODE].to_numpy(), + np.column_stack( + (np.ones(len(group)), group[days], group[days] * group[hours]) + ), + ) + self.model = fit( + pool, + list(QRF_CHILDCARE_PREDICTORS), + [QRF_CHILDCARE_SCHEDULE_CODE], + weights="child_weight", + seed=seed, + n_estimators=n_estimators, + ) + self.cache = {} + self.decoder_movements = [] + + def prepare(self, children): + """Prepare held-out feature profiles in bounded, deterministic batches.""" + if self.training_households.intersection(children.source_household_id): + raise ValueError("QRF evaluation households overlap training households.") + profiles = children.reindex(columns=QRF_CHILDCARE_PREDICTORS).drop_duplicates() + if not np.isfinite(profiles.to_numpy(dtype=float)).all(): + raise ValueError("QRF target predictors must be finite.") + if not set(profiles.age).issubset(self.ages): + raise ValueError("QRF target has no exact-age training support.") + uniforms = qmc.Sobol(2, scramble=False).random_base2(QRF_CHILDCARE_GRID_POWER) + count = len(uniforms) + for start in range(0, len(profiles), 64): + batch = profiles.iloc[start : start + 64] + repeated = batch.loc[batch.index.repeat(count)].reset_index(drop=True) + codes = ( + self.model.predict_from_uniforms( + repeated, + quantiles={ + QRF_CHILDCARE_SCHEDULE_CODE: np.tile(uniforms[:, 1], len(batch)) + }, + sign_uniforms={ + QRF_CHILDCARE_SCHEDULE_CODE: np.tile(uniforms[:, 0], len(batch)) + }, + )[QRF_CHILDCARE_SCHEDULE_CODE] + .to_numpy() + .reshape(len(batch), count) + ) + for row, draws in zip( + batch.itertuples(index=False, name=None), codes, strict=True + ): + values = np.zeros((count, 3)) + positive = draws > 0 + if positive.any(): + if row[0] not in self.catalogs: + raise ValueError( + "Positive QRF draw has no exact-age positive donor." + ) + catalog, schedules = self.catalogs[row[0]] + right = np.clip( + np.searchsorted(catalog, draws[positive]), 0, len(catalog) - 1 + ) + left = np.maximum(right - 1, 0) + chosen = np.where( + np.abs(catalog[left] - draws[positive]) + <= np.abs(catalog[right] - draws[positive]), + left, + right, + ) + values[positive] = schedules[chosen] + self.decoder_movements.extend( + np.abs(catalog[chosen] - draws[positive]).tolist() + ) + values, counts = np.unique(values, axis=0, return_counts=True) + probabilities = counts / count + cumulative = probabilities.cumsum() + cumulative[-1] = 1.0 + self.cache[row] = (values, cumulative, probabilities) + + def distribution(self, child): + """Return the prepared empirical integration distribution.""" + key = tuple(child.reindex(QRF_CHILDCARE_PREDICTORS)) + if child.source_household_id in self.training_households: + raise ValueError("QRF evaluation households overlap training households.") + return self.cache[key] diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py index 46280adc4..102e080ad 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_sibling_validation.py @@ -114,6 +114,7 @@ def assess_sibling_schedules( pooled=False, include_observed_children=False, pooling_fit_objective="pair_squared_error", + composition=False, ): """Evaluate pair intensity and larger-household totals with disjoint training. @@ -122,6 +123,8 @@ def assess_sibling_schedules( merely because a new split is used. """ children = source.children.loc[source.children.age.between(0, 12)].copy() + if composition and not pooled: + raise ValueError("Composition matching requires the pooled model.") if pooled or include_observed_children: from microcosm.build.us_runtime.nsece_childcare_pooling import ( PooledScheduleDonors, @@ -159,14 +162,18 @@ def assess_sibling_schedules( } ) fitted = ( - fit_pooled_sibling_dependence(training, objective=pooling_fit_objective) + fit_pooled_sibling_dependence( + training, objective=pooling_fit_objective, composition=composition + ) if pooled else fit_nsece_sibling_dependence(training, match_columns=match_columns) ) rho = fitted["rho"] dependence_fits.append(fitted) fold_fits.append(rho) - pooled_model = PooledScheduleDonors(training) if pooled else None + pooled_model = ( + PooledScheduleDonors(training, composition=composition) if pooled else None + ) train = training.loc[training.attendance_status.eq("complete")].sort_values( [days, hours, "donor_id"] ) @@ -382,18 +389,23 @@ def distribution( result["diagnostic_screen"] = sibling_schedule_screen(result) if pooled: from microcosm.build.us_runtime.nsece_childcare_pooling import ( + COMPOSITION_CHILDCARE_LEVELS, POOLED_CHILDCARE_LEVELS, POOLED_CHILDCARE_STRENGTH, ) + pooling_levels = ( + COMPOSITION_CHILDCARE_LEVELS if composition else POOLED_CHILDCARE_LEVELS + ) result["model"] = { "name": "partially_pooled_empirical_schedules", - "levels": POOLED_CHILDCARE_LEVELS, + "levels": pooling_levels, + "composition": composition, "strength": POOLED_CHILDCARE_STRENGTH, "dependence_objective": pooling_fit_objective, "dependence_fits": dependence_fits, } - result["match_columns"] = POOLED_CHILDCARE_LEVELS[-1] + result["match_columns"] = pooling_levels[-1] result["fallback_match_columns"] = () if include_observed_children: result["matching_count_universe"] = ( diff --git a/packages/microcosm-build/tests/test_us_childcare_pooling.py b/packages/microcosm-build/tests/test_us_childcare_pooling.py index f842a444b..4e73d8a0d 100644 --- a/packages/microcosm-build/tests/test_us_childcare_pooling.py +++ b/packages/microcosm-build/tests/test_us_childcare_pooling.py @@ -13,6 +13,7 @@ ) from microcosm.build.us_runtime.nsece_childcare import NSECEChildcareSource from microcosm.build.us_runtime.nsece_childcare_pooling import ( + COMPOSITION_CHILDCARE_LEVELS, POOLED_CHILDCARE_LEVELS, POOLED_CHILDCARE_MATCH_COLUMNS, PooledScheduleDonors, @@ -111,6 +112,31 @@ def test_roster_count_includes_unknown_attendance_but_not_older_children(): assert "childcare_household_size" not in children +def test_composition_counts_full_roster_and_never_reads_attendance(): + rows = _children(1) + rows["age"] = [1, 4, 9] + rows.loc[2, "attendance_status"] = "partial_calendar" + counted = with_childcare_household_size(rows) + assert counted.childcare_under6_count.tolist() == [2, 2, 2] + assert counted.childcare_schoolage_count.tolist() == [1, 1, 1] + assert counted.childcare_youngest_age_band.tolist() == [0, 0, 0] + assert counted.childcare_has_younger_sibling.tolist() == [0, 1, 1] + rows["attendance_status"] = "missing_calendar" + changed = with_childcare_household_size(rows) + columns = COMPOSITION_CHILDCARE_LEVELS[-1] + assert_frame_equal(counted[list(columns)], changed[list(columns)]) + + +def test_composition_distributions_exclude_the_whole_household_and_retain_age(): + rows = _children() + model = PooledScheduleDonors(rows, composition=True) + values, _, weights = model.distribution(rows.iloc[0], exclude_household="h0") + assert model.levels == COMPOSITION_CHILDCARE_LEVELS + assert len(values) == len(model.pool.loc[model.pool.source_household_id.ne("h0")]) + assert weights.sum() == pytest.approx(1) + assert set(map(tuple, values)) == {(0, 0, 0), (1, 5, 40)} + + def test_dependence_uses_observed_pairs_in_incomplete_households(monkeypatch): original = PooledScheduleDonors.distribution exclusions = [] diff --git a/packages/microcosm-build/tests/test_us_childcare_qrf.py b/packages/microcosm-build/tests/test_us_childcare_qrf.py new file mode 100644 index 000000000..c44515623 --- /dev/null +++ b/packages/microcosm-build/tests/test_us_childcare_qrf.py @@ -0,0 +1,115 @@ +"""Synthetic contracts for the experimental canonical-QRF schedule decoder.""" + +import numpy as np +import pandas as pd +import pytest + +from microcosm.build.us_runtime import nsece_childcare_qrf as module +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.nsece_childcare_pooling import ( + with_childcare_household_size, +) + +MONTH, DAYS, HOURS = US_CHILDCARE_ATTENDANCE_COLUMNS + + +def _rows(): + rows = pd.DataFrame( + { + "donor_id": ["a", "b", "c", "d"], + "source_household_id": ["a", "b", "c", "d"], + "age": [3, 3, 3, 4], + "region": 1, + "parent_work_status": 2, + "income_band": 1, + "household_income": 40000, + "household_members": 3, + "resident_parent_count": 2, + "attendance_status": "complete", + "child_weight": [1.0, 2.0, 3.0, 4.0], + MONTH: [0.0, 4.0, 9.0, 22.0], + DAYS: [0.0, 1.0, 2.0, 5.0], + HOURS: [0.0, 1.0, 2.0, 10.0], + } + ) + return with_childcare_household_size(rows) + + +class _FakeFit: + def predict_from_uniforms(self, rows, *, quantiles, sign_uniforms): + key = module.QRF_CHILDCARE_SCHEDULE_CODE + assert set(quantiles) == set(sign_uniforms) == {key} + return pd.DataFrame({key: np.resize([0.0, 39.0, 100.0], len(rows))}) + + +def test_decoder_retains_exact_age_joint_pairs_and_lower_ties(monkeypatch): + captured = [] + + def fit(rows, predictors, targets, **kwargs): + captured.append((rows.copy(), predictors, targets, kwargs)) + return _FakeFit() + + monkeypatch.setattr(module, "fit", fit) + model = module.QRFChildcareSchedules(_rows()) + target = _rows().iloc[[0]].assign(source_household_id="new") + model.prepare(target) + values, cumulative, weights = model.distribution(target.iloc[0]) + assert set(map(tuple, values)) == { + (0.0, 0.0, 0.0), + (1.0, 1.0, 1.0), + (1.0, 2.0, 4.0), + } + assert cumulative[-1] == 1 + assert weights.sum() == pytest.approx(1) + assert captured[0][3] == { + "weights": "child_weight", + "seed": 915, + "n_estimators": 100, + } + assert captured[0][1] == list(module.QRF_CHILDCARE_PREDICTORS) + + +def test_qrf_cannot_score_its_own_training_households(monkeypatch): + monkeypatch.setattr(module, "fit", lambda *a, **k: _FakeFit()) + rows = _rows() + model = module.QRFChildcareSchedules(rows) + with pytest.raises(ValueError, match="overlap"): + model.prepare(rows) + with pytest.raises(ValueError, match="overlap"): + model.distribution(rows.iloc[0]) + with pytest.raises(ValueError, match="exact-age"): + model.prepare(rows.iloc[[0]].assign(source_household_id="new", age=9)) + + +def test_qrf_excludes_unknown_calendars_and_rejects_reconstructed_truth(monkeypatch): + captured = [] + + def fit(rows, *args, **kwargs): + captured.append(rows.copy()) + return _FakeFit() + + monkeypatch.setattr(module, "fit", fit) + rows = _rows() + rows.loc[1, "attendance_status"] = "partial_calendar" + rows.loc[1, [MONTH, DAYS, HOURS]] = np.nan + module.QRFChildcareSchedules(rows) + assert captured[0].donor_id.tolist() == ["a", "c", "d"] + rows.loc[1, "attendance_status"] = "summary_bridge" + with pytest.raises(ValueError, match="original measured"): + module.QRFChildcareSchedules(rows) + + +def test_real_canonical_model_gives_valid_row_order_invariant_schedules(): + model = module.QRFChildcareSchedules(_rows(), n_estimators=4) + targets = _rows().iloc[[0, 3]].assign(source_household_id=["new1", "new2"]) + model.prepare(targets) + first = model.distribution(targets.iloc[0]) + model.prepare(targets.iloc[::-1]) + second = model.distribution(targets.iloc[0]) + for a, b in zip(first, second, strict=True): + np.testing.assert_array_equal(a, b) + assert set(map(tuple, first[0])).issubset( + {(0.0, 0.0, 0.0), (1.0, 1.0, 1.0), (1.0, 2.0, 4.0)} + ) diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index 6920cfc88..0b3b5d235 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -131,6 +131,56 @@ def test_complete_parental_care_is_a_weighted_zero_donor(): assert weights.total == 100 +def test_calendar_bounds_preserve_measured_blocks_without_filling_ambiguous_time(): + hh, cal = _raw() + _care(cal, hours=4) + cal.loc[0, "HH4_CHCAL_R_1_200"] = 97 + child = derive_nsece_childcare(hh, cal).children.iloc[0] + assert child.calendar_ece_hours_lower == 4 + assert child.calendar_ece_hours_upper == 4.25 + assert child.calendar_ece_days_lower == 1 + assert child.calendar_ece_days_upper == 2 + assert child.calendar_unknown_hours == 0.25 + assert child.attendance_status == "ambiguous_calendar" + assert pd.isna(child[DAYS]) + + +def test_partial_calendar_assumed_parental_zeros_remain_unresolved(): + hh, cal = _raw() + _care(cal, hours=4) + hh["HH4_MISSING_STATUS_CC_1"] = 1 + child = derive_nsece_childcare(hh, cal).children.iloc[0] + assert child.calendar_ece_hours_lower == 4 + assert child.calendar_ece_hours_upper == 168 + assert child.calendar_ece_days_lower == 1 + assert child.calendar_ece_days_upper == 7 + assert child.calendar_unknown_hours == 164 + assert pd.isna(child.regular_hours_per_week) + assert pd.isna(child[HOURS]) + + +def test_missing_calendar_has_uninformative_bounds_not_observed_zeros(): + hh, cal = _raw() + hh["HH4_MISSING_STATUS_CC_1"] = 0 + child = derive_nsece_childcare(hh, cal).children.iloc[0] + assert child.calendar_ece_hours_lower == 0 + assert child.calendar_ece_hours_upper == 168 + assert child.calendar_ece_days_lower == 0 + assert child.calendar_ece_days_upper == 7 + assert pd.isna(child[DAYS]) + + +def test_complete_calendar_bounds_equal_the_observed_schedule(): + hh, cal = _raw() + _care(cal, hours=4) + child = derive_nsece_childcare(hh, cal).children.iloc[0] + assert child.calendar_ece_hours_lower == child.ece_hours_per_week + assert child.calendar_ece_hours_upper == child.ece_hours_per_week + assert child.calendar_ece_days_lower == child[DAYS] + assert child.calendar_ece_days_upper == child[DAYS] + assert child.calendar_unknown_hours == 0 + + def test_unknown_provider_type_is_not_a_zero_schedule(): hh, cal = _raw() _care(cal) diff --git a/packages/microcosm-build/tests/test_us_spine_blindness.py b/packages/microcosm-build/tests/test_us_spine_blindness.py index 1cab00db0..27e97da46 100644 --- a/packages/microcosm-build/tests/test_us_spine_blindness.py +++ b/packages/microcosm-build/tests/test_us_spine_blindness.py @@ -264,6 +264,7 @@ "nsece_childcare_sibling_validation.py", # Whole-household schedule diagnostics. "nsece_childcare_dependence.py", # Household dependence estimation. "nsece_childcare_pooling.py", # Experimental donor pooling; no source-spine routing. + "nsece_childcare_qrf.py", # Experimental canonical conditional matching. "nsece_childcare_bridge.py", # Source measurement completion; no spine routing. "operator_boundary.py", # Raw-stage validator; no population treatment. "org_wages.py", diff --git a/tools/validate_us_childcare_calendar_selection.py b/tools/validate_us_childcare_calendar_selection.py new file mode 100644 index 000000000..e3479d7da --- /dev/null +++ b/tools/validate_us_childcare_calendar_selection.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Audit calendar information loss and compare the declared composition model.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + +import numpy as np + +from microcosm.build.us_runtime import ( + nsece_childcare_pooling, + nsece_childcare_sibling_validation, +) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + attendance_recipe_identity, +) +from microcosm.build.us_runtime.nsece_childcare import load_nsece_childcare +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + assess_sibling_schedules, + coupling_screen_compatibility, +) + + +def calendar_bounds_report(children): + """Design-weighted identification intervals; no missing outcome imputation.""" + selected = children.loc[ + children.age.between(0, 12) & children.questionnaire_version.eq(1) + ] + rows = [] + for name, group in [ + ("all_regular_instrument", selected), + *selected.groupby("attendance_status"), + ]: + weights = group.child_weight.to_numpy() + rows.append( + { + "group": name, + "children": len(group), + "child_weight": float(weights.sum()), + "mean_hours_lower": float( + np.average(group.calendar_ece_hours_lower, weights=weights) + ), + "mean_hours_upper": float( + np.average(group.calendar_ece_hours_upper, weights=weights) + ), + "mean_days_lower": float( + np.average(group.calendar_ece_days_lower, weights=weights) + ), + "mean_days_upper": float( + np.average(group.calendar_ece_days_upper, weights=weights) + ), + "definitely_in_care_share": float( + np.average(group.calendar_ece_hours_lower.gt(0), weights=weights) + ), + "possibly_in_care_share": float( + np.average(group.calendar_ece_hours_upper.gt(0), weights=weights) + ), + "hours_interval_width_le_1_share": float( + np.average( + ( + group.calendar_ece_hours_upper + - group.calendar_ece_hours_lower + ).le(1), + weights=weights, + ) + ), + } + ) + return { + "interpretation": "regular questionnaire only; measured calendar classifications conditional on published completeness status; identification bounds, not confidence intervals or completed schedules", + "comparisons": rows, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("Report path must be new") + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + plan = ( + Path(__file__).resolve().parents[1] + / "experiments/us-childcare-attendance/calendar-selection-plan.txt" + ) + report = { + "source": source.source_receipt, + "plan_sha256": hashlib.sha256(plan.read_bytes()).hexdigest(), + "recipe": attendance_recipe_identity(), + "diagnostic_code_sha256": { + path.name: hashlib.sha256(path.read_bytes()).hexdigest() + for path in ( + Path(__file__), + Path(nsece_childcare_pooling.__file__), + Path(nsece_childcare_sibling_validation.__file__), + ) + }, + "production_ready": False, + "interpretation": "fixed model comparison on previously inspected development data; no independent validation claim", + "calendar_bounds": calendar_bounds_report(source.children), + } + print(json.dumps(report["calendar_bounds"]), flush=True) + for name, composition in (("pooled_moments", False), ("composition", True)): + arm = assess_sibling_schedules( + source, + pooled=True, + composition=composition, + include_observed_children=True, + pooling_fit_objective="population_moments", + ) + arm["dependence_only_screen_compatibility"] = coupling_screen_compatibility(arm) + report[name] = arm + print( + name, + "joint failures", + sum(not c["passed"] for c in arm["diagnostic_screen"]["checks"]), + "child failures", + sum( + not c["passed"] + for c in arm["observed_child_validation"]["screen"]["checks"] + ), + flush=True, + ) + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tools/validate_us_childcare_qrf.py b/tools/validate_us_childcare_qrf.py new file mode 100644 index 000000000..04677da63 --- /dev/null +++ b/tools/validate_us_childcare_qrf.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Run the declared canonical-QRF marginal diagnostic on licensed local data.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.us_runtime import nsece_childcare_qrf +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + attendance_recipe_identity, +) +from microcosm.build.us_runtime.nsece_childcare import load_nsece_childcare +from microcosm.build.us_runtime.nsece_childcare_pooling import ( + with_childcare_household_size, +) +from microcosm.build.us_runtime.nsece_childcare_qrf import ( + QRF_CHILDCARE_PREDICTORS, + QRFChildcareSchedules, +) +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + _household_splits, + _observed_child_summary, +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("Report path must be new") + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + raw = pd.read_csv( + args.household_tsv, + sep="\t", + usecols=["HH4_METH_CASEID", "HH4_HHCOMP_MEMBERS", "HH4_HHCOMP_NUMPARENTS"], + ) + raw.index = raw.HH4_METH_CASEID.astype(str) + children = with_childcare_household_size( + source.children.loc[source.children.age.between(0, 12)] + ) + children["household_members"] = children.source_household_id.map( + raw.HH4_HHCOMP_MEMBERS + ) + children["resident_parent_count"] = children.source_household_id.map( + raw.HH4_HHCOMP_NUMPARENTS + ) + records, folds, movements = [], [], [] + for fold, (train_mask, target_mask) in enumerate( + _household_splits(children, seed=271828, validation_seed=None, partition="all") + ): + training, target = children.loc[train_mask], children.loc[target_mask] + complete = target.groupby("source_household_id").attendance_status.agg( + lambda x: x.eq("complete").all() + ) + target = target.loc[target.attendance_status.eq("complete")] + model = QRFChildcareSchedules(training) + model.prepare(target) + for _, child in target.iterrows(): + values, _, probability = model.distribution(child) + actual = np.array( + [ + float(child.childcare_days_per_week > 0), + child.childcare_days_per_week, + child.ece_hours_per_week, + ] + ) + records.append( + ( + float(child.child_weight), + min(int(child.childcare_household_size), 3), + bool(complete.loc[child.source_household_id]), + actual, + np.average(values, axis=0, weights=probability), + np.average(values**2, axis=0, weights=probability), + ) + ) + folds.append( + { + "fold": fold, + "training_households": int(training.source_household_id.nunique()), + "evaluation_households": int(target.source_household_id.nunique()), + "household_overlap": 0, + "evaluated_children": len(target), + "profiles": len(model.cache), + } + ) + movements.extend(model.decoder_movements) + print("completed fold", fold, flush=True) + plan = ( + Path(__file__).resolve().parents[1] + / "experiments/us-childcare-attendance/qrf-calendar-plan.txt" + ) + report = { + "source": source.source_receipt, + "recipe": attendance_recipe_identity(), + "plan_sha256": hashlib.sha256(plan.read_bytes()).hexdigest(), + "diagnostic_code_sha256": { + p.name: hashlib.sha256(p.read_bytes()).hexdigest() + for p in [Path(__file__), Path(nsece_childcare_qrf.__file__)] + }, + "model": { + "name": "canonical_weighted_QRF_joint_schedule_decoder", + "predictors": QRF_CHILDCARE_PREDICTORS, + "trees": 100, + "seed": 915, + "integration_points": 128, + "mean_positive_decoder_code_movement": float(np.mean(movements)), + "max_positive_decoder_code_movement": float(np.max(movements)), + }, + "splits": folds, + "observed_child_validation": _observed_child_summary(records), + "production_ready": False, + "interpretation": "marginal development diagnostic only; no dependence fit, no source-selection correction, no population integration or independent validation", + } + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + print( + "child failures", + sum( + not c["passed"] + for c in report["observed_child_validation"]["screen"]["checks"] + ), + flush=True, + ) + + +if __name__ == "__main__": + main() diff --git a/tools/verify_us_childcare_candidate.py b/tools/verify_us_childcare_candidate.py new file mode 100644 index 000000000..969e6fff5 --- /dev/null +++ b/tools/verify_us_childcare_candidate.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Verify a saved attendance candidate through both native population loaders. + +This checks artifact integrity and preservation, not statistical validity. The +report contains hashes and aggregate counts only; source records stay local. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from importlib.metadata import version +from pathlib import Path + +import numpy as np +import pandas as pd + +from microcosm.build.frame_checkpoint import load_frame_checkpoint +from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, +) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, +) +from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 +from microcosm.build.us_runtime.l0_refit_export import load_us_frame +from microcosm.frame import Frame + + +def _sha256(path): + with path.open("rb") as stream: + return hashlib.file_digest(stream, "sha256").hexdigest() + + +def _assert_table_equal(expected, actual): + # HDF may restore strings in Python rather than Arrow storage. Normalize + # that backend only; retain null semantics, values and all other dtypes. + tables = [] + for original in (expected, actual): + table = original.copy() + for column in table: + dtype = table[column].dtype + if isinstance(dtype, pd.StringDtype): + table[column] = table[column].astype( + pd.StringDtype(storage="python", na_value=dtype.na_value) + ) + tables.append(table) + pd.testing.assert_frame_equal(*tables, check_exact=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--parent-h5", type=Path, required=True) + parser.add_argument("--parent-sha256", required=True) + parser.add_argument("--candidate-checkpoint", type=Path, required=True) + parser.add_argument("--candidate-native-h5", type=Path, required=True) + parser.add_argument("--source-stage-report", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("Report path must be new") + if _sha256(args.parent_h5) != args.parent_sha256: + parser.error("Parent population hash mismatch") + source = json.loads(args.source_stage_report.read_text()) + checkpoint_hash = _sha256(args.candidate_checkpoint) + native_hash = _sha256(args.candidate_native_h5) + for key, actual in ( + ("parent_population_sha256", args.parent_sha256), + ("candidate_checkpoint_sha256", checkpoint_hash), + ("native_candidate_sha256", native_hash), + ): + if source[key] != actual: + raise ValueError(f"Source-stage report does not bind {key}.") + if ( + not source["production_stage_executed"] + or not source["native_candidate_written"] + ): + raise ValueError("Source-stage report does not attest a native stage export.") + parent = load_legacy_calibrated_us_h5(args.parent_h5) + stored = load_frame_checkpoint(args.candidate_checkpoint) + frame = stored.frame + candidate = Frame( + {entity: frame.table(entity) for entity in frame.entities}, + frame.schema, + {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata=stored.metadata["frame_metadata"], + ) + binding = assert_bound_childcare_attendance(candidate) + columns = list(US_CHILDCARE_ATTENDANCE_COLUMNS) + verified_loaders = [] + for loader in (load_legacy_calibrated_us_h5, load_us_frame): + loaded = loader(args.candidate_native_h5) + restored = assert_bound_childcare_attendance(loaded) + if restored["binding_sha256"] != binding["binding_sha256"]: + raise ValueError("Native reload changed the attendance binding.") + _assert_table_equal( + loaded.table("person")[columns], candidate.table("person")[columns] + ) + for entity in parent.entities: + original = parent.table(entity) + _assert_table_equal(original, candidate.table(entity)[original.columns]) + _assert_table_equal(original, loaded.table(entity)[original.columns]) + for entity in parent.weighted_entities: + original_weights = parent.weights_for(entity) + for restored_frame in (candidate, loaded): + actual_weights = restored_frame.weights_for(entity) + if actual_weights.kind != original_weights.kind: + raise ValueError("Candidate changed the original weight kind.") + np.testing.assert_array_equal( + original_weights.values, actual_weights.values + ) + verified_loaders.append(loader.__module__ + "." + loader.__name__) + people = candidate.table("person") + result = { + "parent_sha256": args.parent_sha256, + "checkpoint_sha256": checkpoint_hash, + "native_sha256": native_hash, + "source_stage_report_sha256": _sha256(args.source_stage_report), + "verification_code_sha256": _sha256(Path(__file__)), + "attendance_recipe": binding["execution"]["recipe"], + "content_binding_sha256": binding["binding_sha256"], + "engine_version": version("policyengine-us"), + "core_version": version("policyengine-core"), + "people": len(people), + "households": len(candidate.table("household")), + "under13_children": int(people.age.between(0, 12).sum()), + "verified_native_loaders": verified_loaders, + "all_original_columns_and_weights_preserved": True, + "all_attendance_values_preserved_on_native_reload": True, + "comparison": "exact values and dtypes, normalizing only pandas string storage backend", + "interpretation": "artifact integrity and preservation only; no statistical certification or publication", + "production_ready": False, + } + args.report.write_text(json.dumps(result, indent=2, allow_nan=False) + "\n") + print(json.dumps(result, indent=2)) + + +if __name__ == "__main__": + main() From 11f2aa343b5fa8fe8578eb90304bef4ef2b963e7 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Wed, 16 Sep 2026 20:05:43 -0400 Subject: [PATCH 10/16] Test interval-informed attendance and fixed-donor sensitivity --- docs/us-childcare-attendance.md | 30 +- experiments/us-childcare-attendance/README.md | 123 +- .../interval-and-paired-plan.txt | 49 + .../interval-training-validation.json | 1487 ++++++++ .../paired-identity-sensitivity.json | 3231 +++++++++++++++++ .../build/us_runtime/childcare_sensitivity.py | 73 + .../build/us_runtime/nsece_childcare_qrf.py | 157 +- .../tests/test_us_childcare_qrf.py | 83 + .../tests/test_us_nsece_childcare.py | 107 + tools/validate_us_childcare_intervals.py | 125 + tools/validate_us_childcare_qrf.py | 26 +- tools/validate_us_childcare_sensitivity.py | 70 +- 12 files changed, 5503 insertions(+), 58 deletions(-) create mode 100644 experiments/us-childcare-attendance/interval-and-paired-plan.txt create mode 100644 experiments/us-childcare-attendance/interval-training-validation.json create mode 100644 experiments/us-childcare-attendance/paired-identity-sensitivity.json create mode 100644 tools/validate_us_childcare_intervals.py diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index 70a185c1e..f14280b34 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -23,6 +23,14 @@ reduces all-zero results from 31 jurisdictions to two (MD and NV). The the current population, sensitivity and model diagnostics. Older 1.819.0 reports remain historical; their receipts are not reused under the new runtime. +The latest diagnostic keeps each child's selected donor fixed when varying +modeled schedules. It flags IL and TN for irregular-care sensitivity, plus an OK +day sensitivity of only about $2. An interval-informed QRF experiment uses the +measured bounds of incomplete training calendars, but still underpredicts weekly +hours for observed children with unresolved siblings by 37.7%. All three tested +completion assumptions fail that subgroup's screens; none is integrated into +the population candidate. + ## Source and mapping The [2024 NSECE V1 release](https://www.childandfamilydataarchive.org/cfda/archives/cfda/studies/39466/datadocumentation) @@ -237,14 +245,20 @@ uv run python tools/validate_us_childcare_sensitivity.py \ --household-tsv /local/39466-0005-Data.tsv \ --calendar-tsv /local/39466-0004-Data.tsv \ --asec-source-cache /local/asec --seed 915 \ + --candidate-checkpoint /local/attendance-checkpoint.h5 \ --year 2026 --report /local/transport-sensitivity.json ``` The alternatives remove modeled irregular hours or shift modeled attended days by one in either direction, subject to hours/day feasibility. Each preserves -measured regular hours. All arms use the same source population, matching fields, -weights and random seed; modified bridge donors are transferred again. These -are assumption stress tests, not confidence intervals. The +measured regular hours. All arms retain the same donor identities and population +weights. Candidate donor labels must match the original donor values; measured +recipient cells and measured-calendar donors remain unchanged. Inconsistent +lineage or observed constraints cause the comparison to fail. Without an explicit +checkpoint, the tool draws the baseline once before applying paired changes. +The older reports re-sorted and transferred modified donors at fixed random ranks; +those reports include donor reassignment as well as schedule changes. Both are +assumption stress tests, not confidence intervals. The [declared diagnostic screens](../experiments/us-childcare-attendance/review-validation-criteria.txt) flag national changes above 10% or state changes above 20% against the candidate, and separately assess sibling schedule distributions. They were written before @@ -259,6 +273,16 @@ income/age/work/region comparisons, masked-calendar reconstruction, sibling join attendance, full target support, and all-state benefit comparisons. Development used these diagnostics; they are not an untouched external acceptance sample. +`tools/validate_us_childcare_intervals.py` evaluates a separate training-only +experiment. It conditions a QRF/empirical schedule distribution on incomplete +calendars' bounds, divides each child's design weight over eight modeled rows, +and refits once. Whole evaluation households are excluded from initial fitting, +completion and refitting. Unsupported intervals stay unknown. Modeled rows carry +`interval_model` status and require explicit experimental opt-in; they are never +scored as observations. Lower- and higher-hours tilts expose sensitivity to the +unidentified coarsening assumption. All arms retain the original subgroup screens +and fail three of 18; this experiment does not alter the production matcher. + Calendar selection remains unidentifiable for excluded ambiguous/partial cases. Conditional matching assumes their schedules resemble supported children with similar covariates. Bridged schedules are modeled, despite observed regular hours. diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index 56693b86c..e1cfead84 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,9 +1,11 @@ -# NSECE attendance: calendar selection and current-runtime validation — 2026-09-16 +# NSECE attendance: interval training and paired sensitivity — 2026-09-16 **PR #916 remains draft.** The population has now been rebuilt from the pinned -parent under **PolicyEngine-US 2.2.1 / Core 3.32.5**. Two additional marginal -models were tested, but neither resolves the observed-child selection problem; -neither replaces the existing production-stage matcher. No population is +parent under **PolicyEngine-US 2.2.1 / Core 3.32.5**. The latest experiment uses +incomplete calendars' measured bounds in training, but still fails the three +unresolved-sibling checks. The new sensitivity comparison holds donor identities +fixed and flags three state comparisons, including one with a $2 effect. No +experimental model replaces the production-stage matcher, no population is published, and all reports retain `production_ready: false`. ## Calendar information that can actually be recovered @@ -73,6 +75,48 @@ missingness effect. - [QRF plan recorded before its results](qrf-calendar-plan.txt) - [QRF model, fold counts and marginal diagnostics](qrf-calendar-validation.json) +## Using partial-calendar information in QRF training + +The next declared experiment conditions the complete-calendar QRF distribution +on each incomplete training child's measured day/hour bounds. Before conditioning, +it mixes 90% QRF and 10% design-weighted exact-age empirical schedules so finite +prediction grids do not remove all observed tail support. Each supported child +contributes eight deterministic midpoint-quantile schedules at one eighth of its +design weight. These rows are labeled `interval_model`, never `complete`. +Unsupported intervals remain unknown; schedules are not clipped into bounds. +The canonical QRF is refitted once, using only training households. + +This assumes a conditional coarsening mechanism that the survey does not +identify. Two declared alternatives tilt the conditional schedule probabilities +by `exp(-weekly_hours / 40)` and `exp(weekly_hours / 40)`. The scale, mixture and +number of draws were fixed before this run. These alternatives expose sensitivity +to assumptions; they are not confidence limits or multiple-imputation uncertainty +estimates. No held-out household's attendance or bounds enter completion or fitting. + +| Model | Overall weekly-hours mean | Overall conditional-mean hours MSE | Unresolved-sibling weekly-hours mean | Subgroup hours error | Failed child screens / 18 | +| --- | ---: | ---: | ---: | ---: | ---: | +| Measured calendars | 14.240 | — | 23.830 | — | — | +| Complete-only QRF | 14.285 | 495.370 | 15.047 | −36.9% | 3 | +| Interval-informed QRF | 14.319 | 491.239 | 14.848 | −37.7% | 3 | +| Lower-hours tilt | 14.048 | 500.343 | 14.464 | −39.3% | 3 | +| Higher-hours tilt | 14.806 | 498.869 | 15.263 | −36.0% | 3 | + +All arms use the same five household folds and 7,460 observed children. The +complete-only arm reproduces every earlier QRF comparison exactly. Across folds, +793–838 incomplete training children have supported schedules; 27–31 do not. +Supported children account for 94.9–96.2% of incomplete-calendar design weight. +Total weight is conserved and all measured rows remain unchanged. + +The central model reduces overall hours MSE by 0.8% and subgroup hours MSE by +3.0%. Subgroup participation error falls from 18.3 to 16.8 percentage points and +days error from 30.0% to 28.2%, but mean-hours bias worsens. Every arm still fails +all three unresolved-sibling checks, so none is adopted. A favorable aggregate +or a selected tilt would not justify production integration. The remaining +discrepancy cannot be removed by treating modeled completions as observations. + +- [Plan recorded before the interval and paired comparisons](interval-and-paired-plan.txt) +- [All model arms, completion support and unchanged screens](interval-training-validation.json) + ## Population rebuilt under PolicyEngine-US 2.2.1 The current candidate contains **166,321 people, 57,240 households and 31,889 @@ -89,7 +133,7 @@ or caseload estimates. Positive benefits alone do not validate attendance. - [Current source-stage report](calendar-review-2.2.1-source-stage.json) - [Current population and all-state comparison](calendar-review-2.2.1-population.json) - [Current native-loader verification](calendar-review-2.2.1-verification.json) -- [Current noncalendar sensitivity](calendar-review-2.2.1-sensitivity.json) +- [Current paired noncalendar sensitivity](paired-identity-sensitivity.json) Both native loaders verify the saved attendance binding against the current recipe and runtime. Verification compares every attendance value, every original @@ -98,25 +142,36 @@ string-storage backend is normalized for the comparison; values, missingness and other dtypes must match exactly. The source-stage exporter also verifies period preservation. No stale-receipt override is used. -The noncalendar stress test preserves measured regular weekly hours and varies -only reconstructed schedules, then repeats the transfer with the same seed and -weights. Its baseline and candidate results match the population report exactly -in every state, and both reports bind the same current recipe. +The paired noncalendar stress test changes only the modeled bridge components of +each child's already selected donor. It verifies those donor labels against the +candidate's attendance values, preserves measured recipient constraints, and +rejects inconsistent or mixed lineage. This isolates schedule assumptions from +changes in donor identity. Measured-calendar assignments do not change in any +arm. All 31,889 under-13 children have donor assignments, including 9,675 assigned +bridge donors. The baseline and candidate results match the population report +exactly in all 51 jurisdictions, with the same checkpoint hash and current recipe. -| Noncalendar assumption | Annual potential benefits | Change from candidate | -| --- | ---: | ---: | -| No modeled irregular hours | $5.792 billion | −3.43% | -| One fewer modeled day | $5.934 billion | −1.06% | -| One more modeled day | $6.010 billion | +0.20% | - -All three national changes remain below the provisional 10% screen. Six state -comparisons exceed 20%: TN (−36.0%, no irregular hours), IA (+60.0%), KS (−49.4%), -MS (−69.6%) and OK (−40.0%) with one fewer day, and AR (+43.9%) with one more -day. The OK flag represents only about $2 on a roughly $5 baseline; the report -includes absolute changes so tiny denominators are visible. Days and daily-hour -rates interact, so benefit changes need not follow the direction of the day -change. These are assumption scenarios, not confidence intervals, and they do -not resolve the underlying measurement gap. +| Noncalendar assumption | Children whose attendance changes | Annual potential benefits | Change from candidate | +| --- | ---: | ---: | ---: | +| No modeled irregular hours | 1,289 | $5.754 billion | −4.07% | +| One fewer modeled day | 3,725 | $5.997 billion | −0.02% | +| One more modeled day | 4,271 | $6.019 billion | +0.35% | + +All three national changes remain below the unchanged provisional 10% screen. +Three state comparisons exceed the unchanged 20% screen: removing irregular +care changes IL by **−29.4% (−$57.9 million)** and TN by **−36.0% (−$43.3 million)**; +one fewer day changes OK by **−40.0% (−$2.06)**. The OK denominator is only about +$5, so the relative flag has little monetary significance. IL and TN remain +materially sensitive to the unmeasured irregular-care assumption. + +The [previous quantile-coupled report](calendar-review-2.2.1-sensitivity.json) +remains historical evidence. It re-sorted donors after changing their schedules, +so holding random ranks fixed could select different donors. That experiment +flagged six state comparisons; the paired experiment flags three, adding IL and +removing the IA/KS/MS/AR day flags. These are different conditional comparisons, +not a corrected confidence interval or proof that reassignment uncertainty is +absent. Days and daily-hour rates interact, so benefit changes need not follow +the direction of the day change. Neither experiment identifies missing schedules. ## Reproduction and remaining decision @@ -131,6 +186,10 @@ uv run python tools/validate_us_childcare_qrf.py \ --household-tsv /local/39466-0005-Data.tsv \ --calendar-tsv /local/39466-0004-Data.tsv \ --report /local/qrf-calendar.json +uv run python tools/validate_us_childcare_intervals.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --report /local/interval-training.json ``` Rebuild with `tools/prepare_us_childcare_attendance.py --production-stage @@ -140,7 +199,9 @@ parent, both source files and the pinned ASEC cache. Then run `tools/validate_us_childcare_sensitivity.py`, and `tools/verify_us_childcare_candidate.py` on those explicit local artifacts. Each tool's `--help` lists the required file/hash arguments; no report is -overwritten. Only aggregate reports are committed. +overwritten. Supply `--candidate-checkpoint` to the sensitivity tool to reuse the +verified candidate's assignments; otherwise it draws a baseline once and holds +those assignments fixed. Only aggregate reports are committed. **Decision:** retain the draft and fail-closed release checks. The current-runtime rebuild closes the stale-runtime evidence gap. Calendar nonresponse and transport @@ -154,12 +215,14 @@ and care outside ages 0–12 remain separate gaps. ## Code validation for this update -All **1,048 distinct local regression checks** pass in their latest applicable -run: 547 source, attendance, pooling, builder, coverage, export and serializer -checks, plus the final 501-test QRF/architecture rerun. The initial architecture -failure required making the QRF predictor selection explicit; the guard remains -unchanged. The final real-source QRF replay reproduces every earlier metric -exactly and records the final code hash. +All **613 distinct local regression checks** for this update pass: 612 source, +QRF, pooling and full source-spine architecture tests, followed by the additional +donor-order reversal regression. Tests cover interval bounds, conserved weights, +training-household exclusions, explicit modeled-row opt-in, and preservation of +measured donors and recipient constraints under paired scenarios. The preceding +calendar/runtime update passed 1,048 checks; it is historical validation for that +update. The complete-only real-source arm reproduces the earlier QRF metrics +exactly, and both new reports' code hashes match the checked-in code. Repository-wide ruff lint, changed-file formatting, tracked CI inventory and built-wheel source-byte checks pass. A repository-wide formatting check reports diff --git a/experiments/us-childcare-attendance/interval-and-paired-plan.txt b/experiments/us-childcare-attendance/interval-and-paired-plan.txt new file mode 100644 index 000000000..6051f8bae --- /dev/null +++ b/experiments/us-childcare-attendance/interval-and-paired-plan.txt @@ -0,0 +1,49 @@ +Interval-informed attendance and paired schedule sensitivity, 2026-09-16. +Declared after the previous QRF and calendar-bound results, before these runs. +Every previously inspected household remains development evidence. + +1. Isolate schedule sensitivity from reassignment. The prior experiment rebuilds + care-sorted donor CDFs after changing bridge schedules. A fixed random rank + can therefore select another donor. Add a paired contrast which retains the + candidate's selected donor identity and changes only that donor's modeled + bridge components. Validate candidate provenance against the original donor + values, preserve measured recipient cells and measured-calendar donors, and + reject ambiguous/mixed lineage or constraints inconsistent with the scenario. + Keep the previous quantile-coupled experiment as historical evidence; neither + coupling identifies the missing schedules. Do not change the 10% national or + 20% state screens. Report absolute effects and the number of changed children. + +2. Use incomplete calendars' measured information in training, without making + their completed schedules into observations. Fit the existing canonical QRF + on complete training calendars. For each ambiguous/partial regular-instrument + training child, condition its predicted joint schedule distribution on the + child's measured lower/upper days and hours. Mix 90% QRF with 10% design-weighted + exact-age empirical schedules before conditioning, to retain empirical tail + support absent from the finite prediction grid. Exclude unsupported intervals + explicitly; never clip an incompatible schedule into its bounds. + +3. Represent each supported incomplete training child by eight midpoint-quantile + draws of that conditional distribution, each with one eighth of its design + weight. Label these interval_model, never complete. Keep complete children and + their weights unchanged. Refit the same 100-tree canonical QRF once. This is + conditional interval imputation, with a coarsening-at-random assumption given + the predictors and observed bounds; that assumption is not identified by the + survey. It is not an EM convergence claim or a completed population. + +4. Assess that assumption with fixed negative/positive exponential tilts of the + incomplete-child distribution: exp(-weekly_hours/40) and exp(weekly_hours/40), + applied only within supported bounds before the eight draws. Report all three + arms, rather than selecting the arm with the most passing checks. Forty hours + is a declared scale, not an estimated coefficient. No response-pattern flag or + held-out attendance/bounds enter the predictors or training completion. + +5. Use the existing five whole-household folds, same 7,460 observed children and + all 18 original child screens. Report conditional-mean and random-draw errors, + including the unresolved-sibling subgroup, plus completion support and weight + conservation in each fold. The evaluated child's household is absent from + initial fitting, completion and refitting. Any promising marginal result still + needs joint validation, target/bridge integration, grid stability, independent + evidence and a new candidate before adoption. Keep the current build unchanged. + +Parent work hours are not silently equated with the NSECE work/school/training +summary, nor are source-only calendar response indicators given to targets. diff --git a/experiments/us-childcare-attendance/interval-training-validation.json b/experiments/us-childcare-attendance/interval-training-validation.json new file mode 100644 index 000000000..4b90dace5 --- /dev/null +++ b/experiments/us-childcare-attendance/interval-training-validation.json @@ -0,0 +1,1487 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "plan_sha256": "c563b275a1bd79166f3458dd6858cdc349287877ba1c6e100605b0c1bd294983", + "diagnostic_code_sha256": { + "validate_us_childcare_intervals.py": "71a6f40234397a38618dd7626429b074a8f32d14664814d5b955293de341d55b", + "validate_us_childcare_qrf.py": "29c2fc33c2ea8bf81d2cf9b9ea179d6704930b2da8c1de7f1fd11918206a3e47", + "nsece_childcare_qrf.py": "f8ae9695a648bd163e04ae2ab92a9cd08ce9c29e7e19ed293c118934ff6ef0b1", + "nsece_childcare_pooling.py": "a779a0db7fddbb9e33aa7f8c8f7752b5f4a083309bb7d8f03d6ab72555a439f2", + "nsece_childcare_sibling_validation.py": "abd2a4c88d3c4585e055fae93be3ac00c060405fc6e24bb079b67196aa88e422" + }, + "folds": [ + { + "fold": 0, + "evaluated_children": 1402, + "household_overlap": 0, + "completions": { + "interval": { + "tilt": 0, + "incomplete_training_children": 869, + "completed_training_children": 838, + "unsupported_training_children": 31, + "unsupported_child_weight": 171444.442584318, + "completed_child_weight": 3695550.4472566973, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "lower_hours": { + "tilt": -1, + "incomplete_training_children": 869, + "completed_training_children": 838, + "unsupported_training_children": 31, + "unsupported_child_weight": 171444.442584318, + "completed_child_weight": 3695550.4472566973, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "higher_hours": { + "tilt": 1, + "incomplete_training_children": 869, + "completed_training_children": 838, + "unsupported_training_children": 31, + "unsupported_child_weight": 171444.442584318, + "completed_child_weight": 3695550.4472566973, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + } + } + }, + { + "fold": 1, + "evaluated_children": 1461, + "household_overlap": 0, + "completions": { + "interval": { + "tilt": 0, + "incomplete_training_children": 821, + "completed_training_children": 793, + "unsupported_training_children": 28, + "unsupported_child_weight": 193038.165822737, + "completed_child_weight": 3581394.364359614, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "lower_hours": { + "tilt": -1, + "incomplete_training_children": 821, + "completed_training_children": 793, + "unsupported_training_children": 28, + "unsupported_child_weight": 193038.165822737, + "completed_child_weight": 3581394.364359614, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "higher_hours": { + "tilt": 1, + "incomplete_training_children": 821, + "completed_training_children": 793, + "unsupported_training_children": 28, + "unsupported_child_weight": 193038.165822737, + "completed_child_weight": 3581394.364359614, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + } + } + }, + { + "fold": 2, + "evaluated_children": 1620, + "household_overlap": 0, + "completions": { + "interval": { + "tilt": 0, + "incomplete_training_children": 844, + "completed_training_children": 817, + "unsupported_training_children": 27, + "unsupported_child_weight": 153841.415545872, + "completed_child_weight": 3632834.4477473935, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "lower_hours": { + "tilt": -1, + "incomplete_training_children": 844, + "completed_training_children": 817, + "unsupported_training_children": 27, + "unsupported_child_weight": 153841.415545872, + "completed_child_weight": 3632834.4477473935, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "higher_hours": { + "tilt": 1, + "incomplete_training_children": 844, + "completed_training_children": 817, + "unsupported_training_children": 27, + "unsupported_child_weight": 153841.415545872, + "completed_child_weight": 3632834.4477473935, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + } + } + }, + { + "fold": 3, + "evaluated_children": 1527, + "household_overlap": 0, + "completions": { + "interval": { + "tilt": 0, + "incomplete_training_children": 852, + "completed_training_children": 823, + "unsupported_training_children": 29, + "unsupported_child_weight": 144714.969021018, + "completed_child_weight": 3657561.416389064, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "lower_hours": { + "tilt": -1, + "incomplete_training_children": 852, + "completed_training_children": 823, + "unsupported_training_children": 29, + "unsupported_child_weight": 144714.969021018, + "completed_child_weight": 3657561.416389064, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "higher_hours": { + "tilt": 1, + "incomplete_training_children": 852, + "completed_training_children": 823, + "unsupported_training_children": 29, + "unsupported_child_weight": 144714.969021018, + "completed_child_weight": 3657561.416389064, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + } + } + }, + { + "fold": 4, + "evaluated_children": 1450, + "household_overlap": 0, + "completions": { + "interval": { + "tilt": 0, + "incomplete_training_children": 838, + "completed_training_children": 810, + "unsupported_training_children": 28, + "unsupported_child_weight": 191452.505919394, + "completed_child_weight": 3641423.917022772, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "lower_hours": { + "tilt": -1, + "incomplete_training_children": 838, + "completed_training_children": 810, + "unsupported_training_children": 28, + "unsupported_child_weight": 191452.505919394, + "completed_child_weight": 3641423.917022772, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + }, + "higher_hours": { + "tilt": 1, + "incomplete_training_children": 838, + "completed_training_children": 810, + "unsupported_training_children": 28, + "unsupported_child_weight": 191452.505919394, + "completed_child_weight": 3641423.917022772, + "draws_per_child": 8, + "empirical_mixture_weight": 0.1, + "design_weight_conserved": true, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption" + } + } + } + ], + "models": { + "complete_only": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.4626963402375239, + "days": 1.8782143152899593, + "weekly_hours": 14.28498839270566 + }, + "conditional_mean_squared_error": { + "participation": 0.22193735839098713, + "days": 4.738177957705935, + "weekly_hours": 495.36993857227833 + }, + "expected_draw_squared_error": { + "participation": 0.3980913088338638, + "days": 8.493289037594954, + "weekly_hours": 884.5670742156525 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5169591695664993, + "days": 2.1040041234632554, + "weekly_hours": 16.180318254505547 + }, + "conditional_mean_squared_error": { + "participation": 0.21309207916173845, + "days": 4.80186267546308, + "weekly_hours": 547.7558761783448 + }, + "expected_draw_squared_error": { + "participation": 0.3939817087645831, + "days": 8.676322304138447, + "weekly_hours": 939.0868577063078 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.47437966009242294, + "days": 1.9431615330846461, + "weekly_hours": 14.443491705680222 + }, + "conditional_mean_squared_error": { + "participation": 0.2237912246436724, + "days": 4.589094013107792, + "weekly_hours": 408.2440342383202 + }, + "expected_draw_squared_error": { + "participation": 0.39728485474399067, + "days": 8.251821584734268, + "weekly_hours": 751.137626070225 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.3965732409177563, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.39813090829428704, + "days": 1.587831290116023, + "weekly_hours": 12.361766263154577 + }, + "conditional_mean_squared_error": { + "participation": 0.22746310337909134, + "days": 4.878935323151912, + "weekly_hours": 563.9256416456769 + }, + "expected_draw_squared_error": { + "participation": 0.40287692057375285, + "days": 8.64909563220651, + "weekly_hours": 1012.7828745314389 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.915354412, + "observed": { + "participation": 0.4440893924635181, + "days": 1.7729192348218485, + "weekly_hours": 13.202436331757639 + }, + "expected": { + "participation": 0.4611924940358734, + "days": 1.8679016105224053, + "weekly_hours": 14.20249149230845 + }, + "conditional_mean_squared_error": { + "participation": 0.21836908152439619, + "days": 4.576891305578152, + "weekly_hours": 458.29836605715 + }, + "expected_draw_squared_error": { + "participation": 0.39552745711675996, + "days": 8.329736490182508, + "weekly_hours": 844.8891652102076 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.8297530885091 + }, + "expected": { + "participation": 0.4765894707182824, + "days": 1.9734871914576904, + "weekly_hours": 15.047127632887687 + }, + "conditional_mean_squared_error": { + "participation": 0.25490252201250563, + "days": 6.22820832142038, + "weekly_hours": 837.8518981731377 + }, + "expected_draw_squared_error": { + "participation": 0.42177719266956654, + "days": 10.004252644889078, + "weekly_hours": 1251.127408916974 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.0024521108330947117, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.0016763946412296814, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.0031295293685603054, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.007251721842393866, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.007309210064487643, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.04324039210977919, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.002206572873478696, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.011487683681736041, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.035997016661823936, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0015576673765307358, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.003259700011982134, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.011229458217074167, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.01710310157235534, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.053574000346440526, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.07574777377605987, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.18311095562033664, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.29988125940976446, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.3685571320441615, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "interval": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.47481553841042756, + "days": 1.9036413478147798, + "weekly_hours": 14.318882355669308 + }, + "conditional_mean_squared_error": { + "participation": 0.21696503199110292, + "days": 4.6544246979093025, + "weekly_hours": 491.23887938195185 + }, + "expected_draw_squared_error": { + "participation": 0.4039248545961482, + "days": 8.520426461063778, + "weekly_hours": 877.6128058327024 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5326740409328169, + "days": 2.172525393673113, + "weekly_hours": 16.552752511708448 + }, + "conditional_mean_squared_error": { + "participation": 0.21012332715092863, + "days": 4.6891218802707675, + "weekly_hours": 521.6097072459198 + }, + "expected_draw_squared_error": { + "participation": 0.3972070493680936, + "days": 8.654124801333941, + "weekly_hours": 906.0406910528437 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.4795097893415978, + "days": 1.9267487111566348, + "weekly_hours": 14.257887223439678 + }, + "conditional_mean_squared_error": { + "participation": 0.21809429079349352, + "days": 4.569188479556899, + "weekly_hours": 412.26761118207486 + }, + "expected_draw_squared_error": { + "participation": 0.40423771240788287, + "days": 8.312462829641136, + "weekly_hours": 758.9488310586797 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.3965732409177563, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.41629758456524774, + "days": 1.629974287966794, + "weekly_hours": 12.38174807397901 + }, + "conditional_mean_squared_error": { + "participation": 0.22164437666318373, + "days": 4.73644843160116, + "weekly_hours": 568.8384378793678 + }, + "expected_draw_squared_error": { + "participation": 0.40957814682084925, + "days": 8.676243139581668, + "weekly_hours": 1009.7627460242041 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.915354412, + "observed": { + "participation": 0.4440893924635181, + "days": 1.7729192348218485, + "weekly_hours": 13.202436331757639 + }, + "expected": { + "participation": 0.4730180301562414, + "days": 1.8905766191709923, + "weekly_hours": 14.261626546610092 + }, + "conditional_mean_squared_error": { + "participation": 0.21394334213323019, + "days": 4.509246185445723, + "weekly_hours": 456.4800074275843 + }, + "expected_draw_squared_error": { + "participation": 0.40143848378205926, + "days": 8.362060155477566, + "weekly_hours": 842.3490382801908 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.8297530885091 + }, + "expected": { + "participation": 0.49142163595434285, + "days": 2.02433851720927, + "weekly_hours": 14.847834335711237 + }, + "conditional_mean_squared_error": { + "participation": 0.244880607015825, + "days": 5.995641648449451, + "weekly_hours": 812.3551896110121 + }, + "expected_draw_squared_error": { + "participation": 0.42689493897430747, + "days": 9.983477499693892, + "weekly_hours": 1203.3935427940266 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.009667087339808977, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.015236965476275973, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.005509652766736848, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.008463149523923685, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.025019830118211506, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.021217954214460908, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.00292355637569619, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.0029442008307291384, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.022684052331692774, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.019724343647491427, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.023195015033406943, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.012864030114329164, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.028928637692723302, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.06636364592263365, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.08022687542182173, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.16827879038427618, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.2818411290777086, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.3769203448914046, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "lower_hours": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.4694631050646648, + "days": 1.8833882492362901, + "weekly_hours": 14.047531587671795 + }, + "conditional_mean_squared_error": { + "participation": 0.21926868145934636, + "days": 4.682031848109813, + "weekly_hours": 500.3427012480258 + }, + "expected_draw_squared_error": { + "participation": 0.4039103799500653, + "days": 8.502006698367383, + "weekly_hours": 881.8243360527052 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.525047960986181, + "days": 2.1357761191991402, + "weekly_hours": 16.054210770550473 + }, + "conditional_mean_squared_error": { + "participation": 0.20936530757532937, + "days": 4.6155487934088635, + "weekly_hours": 523.6047784780147 + }, + "expected_draw_squared_error": { + "participation": 0.39564566426476305, + "days": 8.55184711704171, + "weekly_hours": 914.9077317360562 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.4749117033807294, + "days": 1.9068519255675083, + "weekly_hours": 14.003162317182646 + }, + "conditional_mean_squared_error": { + "participation": 0.21815139862014563, + "days": 4.561931500445223, + "weekly_hours": 422.2683367671114 + }, + "expected_draw_squared_error": { + "participation": 0.40165024136872324, + "days": 8.243826381498717, + "weekly_hours": 766.5250069522581 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.3965732409177563, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.411996000295025, + "days": 1.6241512727038396, + "weekly_hours": 12.293546528799457 + }, + "conditional_mean_squared_error": { + "participation": 0.22970229959955119, + "days": 4.901842706260032, + "weekly_hours": 583.1719294446634 + }, + "expected_draw_squared_error": { + "participation": 0.41438360216783604, + "days": 8.800380403699931, + "weekly_hours": 1005.2928126952421 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.915354412, + "observed": { + "participation": 0.4440893924635181, + "days": 1.7729192348218485, + "weekly_hours": 13.202436331757639 + }, + "expected": { + "participation": 0.468748065741229, + "days": 1.877169949779378, + "weekly_hours": 14.002473817238808 + }, + "conditional_mean_squared_error": { + "participation": 0.21484625757130862, + "days": 4.498558028782448, + "weekly_hours": 463.4897759965144 + }, + "expected_draw_squared_error": { + "participation": 0.40059537458069444, + "days": 8.314169510261438, + "weekly_hours": 843.9903904302846 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.8297530885091 + }, + "expected": { + "participation": 0.4760689232716501, + "days": 1.9408353775586458, + "weekly_hours": 14.463793226069521 + }, + "conditional_mean_squared_error": { + "participation": 0.2601247956353107, + "days": 6.377036103811426, + "weekly_hours": 840.8047102985204 + }, + "expected_draw_squared_error": { + "participation": 0.4345357205537359, + "days": 10.23732149361069, + "weekly_hours": 1231.349402217937 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.004314653994046236, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.004435721656939506, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.013545313237585262, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.0008370695772877967, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.0076811443712098675, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.05069726316775619, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.0016745295851722242, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.007412814386791165, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.004413245775410731, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.015422759377268669, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.019539693453487807, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.005648879880363551, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.02465867327771093, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.05880172819491406, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.06059771585921071, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.1836315030669689, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.3114648901137978, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.3930363788349918, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + }, + "higher_hours": { + "comparisons": [ + { + "group": "all", + "children": 7460, + "child_weight": 32249785.682044946, + "observed": { + "participation": 0.4651484510706186, + "days": 1.8750709563867471, + "weekly_hours": 14.240422572045734 + }, + "expected": { + "participation": 0.4817605698879911, + "days": 1.935495472459631, + "weekly_hours": 14.8055110801012 + }, + "conditional_mean_squared_error": { + "participation": 0.2183109232064996, + "days": 4.690768651747343, + "weekly_hours": 498.8691681881239 + }, + "expected_draw_squared_error": { + "participation": 0.40741923323645185, + "days": 8.605384759130065, + "weekly_hours": 907.0848865837559 + } + }, + { + "group": "household_size_1", + "children": 1910, + "child_weight": 9010809.078107145, + "observed": { + "participation": 0.5242108914088932, + "days": 2.119495964700082, + "weekly_hours": 16.911581677434366 + }, + "expected": { + "participation": 0.5336709141023375, + "days": 2.188482690987151, + "weekly_hours": 16.9836136757819 + }, + "conditional_mean_squared_error": { + "participation": 0.21203549535609473, + "days": 4.757503626364953, + "weekly_hours": 529.4532061228166 + }, + "expected_draw_squared_error": { + "participation": 0.4019308546381216, + "days": 8.800956479349571, + "weekly_hours": 957.0728947317252 + } + }, + { + "group": "household_size_2", + "children": 2845, + "child_weight": 13265562.299413564, + "observed": { + "participation": 0.47658623296590163, + "days": 1.921092628643475, + "weekly_hours": 13.941634457809398 + }, + "expected": { + "participation": 0.4872962043858133, + "days": 1.961324327491388, + "weekly_hours": 14.581394805547957 + }, + "conditional_mean_squared_error": { + "participation": 0.21757171189632124, + "days": 4.574209050120303, + "weekly_hours": 404.8084050090706 + }, + "expected_draw_squared_error": { + "participation": 0.4060140807749717, + "days": 8.346865239290644, + "weekly_hours": 750.762219107529 + } + }, + { + "group": "household_size_3", + "children": 2705, + "child_weight": 9973414.304524235, + "observed": { + "participation": 0.3965732409177563, + "days": 1.5930240706983663, + "weekly_hours": 12.224491842781102 + }, + "expected": { + "participation": 0.427497557376175, + "days": 1.6725710886094731, + "weekly_hours": 13.135728021730285 + }, + "conditional_mean_squared_error": { + "participation": 0.2249638841589955, + "days": 4.7855097818173356, + "weekly_hours": 596.3465172213456 + }, + "expected_draw_squared_error": { + "participation": 0.41424687191029114, + "days": 8.772543899737947, + "weekly_hours": 1069.845164762894 + } + }, + { + "group": "complete_household", + "children": 6699, + "child_weight": 29099899.915354412, + "observed": { + "participation": 0.4440893924635181, + "days": 1.7729192348218485, + "weekly_hours": 13.202436331757639 + }, + "expected": { + "participation": 0.4805926944205894, + "days": 1.9264832406591257, + "weekly_hours": 14.755995861748625 + }, + "conditional_mean_squared_error": { + "participation": 0.21531436658512484, + "days": 4.537187483655545, + "weekly_hours": 462.1342992299814 + }, + "expected_draw_squared_error": { + "participation": 0.40507603278902476, + "days": 8.436042507760133, + "weekly_hours": 870.7039390572138 + } + }, + { + "group": "unresolved_siblings", + "children": 761, + "child_weight": 3149885.766690531, + "observed": { + "participation": 0.659700426338619, + "days": 2.8187892667948593, + "weekly_hours": 23.8297530885091 + }, + "expected": { + "participation": 0.49254986884428775, + "days": 2.0187540611303847, + "weekly_hours": 15.262952398635264 + }, + "conditional_mean_squared_error": { + "participation": 0.24599430737706623, + "days": 6.109612683367641, + "weekly_hours": 838.2405261560589 + }, + "expected_draw_squared_error": { + "participation": 0.4290666527152173, + "days": 10.169835958731479, + "weekly_hours": 1243.1865773318777 + } + } + ], + "screen": { + "passed": false, + "checks": [ + { + "group": "all", + "metric": "participation", + "relative": false, + "gap": 0.016612118817372512, + "limit": 0.05, + "passed": true + }, + { + "group": "all", + "metric": "days", + "relative": true, + "gap": 0.03222518906128307, + "limit": 0.2, + "passed": true + }, + { + "group": "all", + "metric": "weekly_hours", + "relative": true, + "gap": 0.03968200418186654, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "participation", + "relative": false, + "gap": 0.009460022693444259, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_1", + "metric": "days", + "relative": true, + "gap": 0.032548647148205746, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_1", + "metric": "weekly_hours", + "relative": true, + "gap": 0.004259329477363312, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "participation", + "relative": false, + "gap": 0.010709971419911646, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_2", + "metric": "days", + "relative": true, + "gap": 0.020942092144886043, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_2", + "metric": "weekly_hours", + "relative": true, + "gap": 0.04588847524833771, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "participation", + "relative": false, + "gap": 0.0309243164584187, + "limit": 0.05, + "passed": true + }, + { + "group": "household_size_3", + "metric": "days", + "relative": true, + "gap": 0.049934598838945486, + "limit": 0.2, + "passed": true + }, + { + "group": "household_size_3", + "metric": "weekly_hours", + "relative": true, + "gap": 0.07454184522911626, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "participation", + "relative": false, + "gap": 0.0365033019570713, + "limit": 0.05, + "passed": true + }, + { + "group": "complete_household", + "metric": "days", + "relative": true, + "gap": 0.08661647006876091, + "limit": 0.2, + "passed": true + }, + { + "group": "complete_household", + "metric": "weekly_hours", + "relative": true, + "gap": 0.11767218496286139, + "limit": 0.2, + "passed": true + }, + { + "group": "unresolved_siblings", + "metric": "participation", + "relative": false, + "gap": 0.16715055749433128, + "limit": 0.05, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "days", + "relative": true, + "gap": 0.2838222832365773, + "limit": 0.2, + "passed": false + }, + { + "group": "unresolved_siblings", + "metric": "weekly_hours", + "relative": true, + "gap": 0.35950018693247887, + "limit": 0.2, + "passed": false + } + ] + }, + "interpretation": "child-weighted predictions of observed calendars; missing children are not scored as zeros; previously used survey, not external validation" + } + }, + "production_ready": false, + "interpretation": "development comparison; completed training intervals remain modeled under declared coarsening assumptions; no held-out household contributes to completion or refitting" +} diff --git a/experiments/us-childcare-attendance/paired-identity-sensitivity.json b/experiments/us-childcare-attendance/paired-identity-sensitivity.json new file mode 100644 index 000000000..7370cd935 --- /dev/null +++ b/experiments/us-childcare-attendance/paired-identity-sensitivity.json @@ -0,0 +1,3231 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "0e72ff9388f8f59147305f07effec2b99dada0f91599727cc9e58ad4feb6edbb", + "nsece_childcare_bridge.py": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "42e777b0edfc813d79204bc2932fe429d6a48cd2866397560a7386cbee54dadb", + "childcare_attendance_receipt.py": "0950fce7f372ca4b240cd055c931f5f8fe070223fd68504a3ea9cfbc4e16c645" + } + }, + "seed": 915, + "candidate_checkpoint_sha256": "31e4a80cc896aeb769ea034a60e8be68c81bed35a52ff52e27ca62d8cb46f40d", + "assignment_checks": { + "candidate": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9675, + "changed_people": 0, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + }, + "no_modeled_irregular_hours": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9675, + "changed_people": 1289, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + }, + "one_fewer_day": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9675, + "changed_people": 3725, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + }, + "one_more_day": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9675, + "changed_people": 4271, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + } + }, + "coupling": "same donor identities across scenarios; no care-rank reassignment", + "engine_version": "2.2.1", + "policy_year": 2026, + "code_sha256": { + "validate_us_childcare_sensitivity.py": "ce58f0179a9b2732d2adf2335ec4444dd8a1f861b248923e9bed561c7282f983", + "childcare_sensitivity.py": "17a684c51ed48f164006d485aa2177d8c1f03609290e96eae377c625bb6d93cf" + }, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "assumptions": { + "candidate": {}, + "no_modeled_irregular_hours": { + "include_modeled_irregular_hours": false, + "modeled_day_shift": 0, + "measured_regular_hours_preserved": true + }, + "one_fewer_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": -1, + "measured_regular_hours_preserved": true + }, + "one_more_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": 1, + "measured_regular_hours_preserved": true + } + }, + "changed_children": { + "candidate": 0, + "no_modeled_irregular_hours": 1289, + "one_fewer_day": 3725, + "one_more_day": 4271 + }, + "screens": { + "national_relative_change": 0.1, + "state_relative_change": 0.2 + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "no_modeled_irregular_hours": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_fewer_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_more_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "no_modeled_irregular_hours": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_fewer_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_more_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159527.697095435 + }, + "no_modeled_irregular_hours": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159608.281765327 + }, + "one_fewer_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159539.191150486 + }, + "one_more_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 23159192.944776826 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "no_modeled_irregular_hours": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "one_fewer_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 52568414.84511111 + }, + "one_more_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + }, + "no_modeled_irregular_hours": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 27, + "weighted_positive_units": 164627.58703250886, + "annual_modeled_benefits": 603001822.2780222 + }, + "one_fewer_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + }, + "one_more_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 175244.38451184036, + "annual_modeled_benefits": 726014134.44521 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "no_modeled_irregular_hours": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "one_fewer_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "one_more_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "no_modeled_irregular_hours": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "one_fewer_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + }, + "one_more_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 636244010.8081238 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "no_modeled_irregular_hours": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_fewer_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_more_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "no_modeled_irregular_hours": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 3288.039414958679, + "annual_modeled_benefits": 23916791.97702106 + }, + "one_fewer_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.300562065 + }, + "one_more_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.180974036 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 336693781.677099 + }, + "no_modeled_irregular_hours": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 76833.30946851184, + "annual_modeled_benefits": 320343372.927099 + }, + "one_fewer_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 333643169.7693259 + }, + "one_more_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 87314.34071851184, + "annual_modeled_benefits": 352665671.4632517 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "no_modeled_irregular_hours": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_fewer_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_more_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "no_modeled_irregular_hours": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "one_fewer_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "one_more_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "no_modeled_irregular_hours": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_fewer_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_more_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + }, + "no_modeled_irregular_hours": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 138894601.64511302 + }, + "one_fewer_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786118.53858995 + }, + "one_more_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "no_modeled_irregular_hours": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_fewer_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_more_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + }, + "no_modeled_irregular_hours": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + }, + "one_fewer_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + }, + "one_more_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.8273853 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "no_modeled_irregular_hours": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "one_fewer_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + }, + "one_more_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 57457564.33302226 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + }, + "no_modeled_irregular_hours": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 147.92350896229354 + }, + "one_fewer_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 163.49416057119927 + }, + "one_more_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 145.79981145011206 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + }, + "no_modeled_irregular_hours": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 69024044.59310351 + }, + "one_fewer_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + }, + "one_more_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "no_modeled_irregular_hours": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_fewer_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_more_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + }, + "no_modeled_irregular_hours": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + }, + "one_fewer_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + }, + "one_more_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 0.0716373446630314, + "annual_modeled_benefits": 214.55442391989982 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "no_modeled_irregular_hours": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_fewer_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_more_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + }, + "no_modeled_irregular_hours": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + }, + "one_fewer_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + }, + "one_more_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 21289.402694451623, + "annual_modeled_benefits": 274850426.86324066 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + }, + "no_modeled_irregular_hours": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 7256.951193145127, + "annual_modeled_benefits": 30449886.57411563 + }, + "one_fewer_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + }, + "one_more_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + }, + "no_modeled_irregular_hours": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110014.74611533 + }, + "one_fewer_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110017.43153306 + }, + "one_more_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110012.51190349 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "no_modeled_irregular_hours": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_fewer_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_more_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "no_modeled_irregular_hours": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "one_fewer_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "one_more_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "no_modeled_irregular_hours": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_fewer_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_more_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "no_modeled_irregular_hours": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_fewer_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_more_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + }, + "no_modeled_irregular_hours": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59644459.68607285 + }, + "one_fewer_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + }, + "one_more_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59646231.852653645 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "no_modeled_irregular_hours": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "one_fewer_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + }, + "one_more_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386150016.03633535 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "no_modeled_irregular_hours": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_fewer_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_more_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + }, + "no_modeled_irregular_hours": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 465.6641840398204 + }, + "one_fewer_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + }, + "one_more_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "no_modeled_irregular_hours": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "one_fewer_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + }, + "one_more_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 109086186.25780228 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "no_modeled_irregular_hours": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "one_fewer_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 3.0897752604315514 + }, + "one_more_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "no_modeled_irregular_hours": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_fewer_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_more_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "no_modeled_irregular_hours": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "one_fewer_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 560.5847087540619 + }, + "one_more_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "no_modeled_irregular_hours": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_fewer_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_more_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "no_modeled_irregular_hours": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "one_fewer_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "one_more_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + }, + "no_modeled_irregular_hours": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 2978.082701569656, + "annual_modeled_benefits": 5809175.488665467 + }, + "one_fewer_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + }, + "one_more_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809177.944389787 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + }, + "no_modeled_irregular_hours": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 37178.86130036996, + "annual_modeled_benefits": 76918628.79092205 + }, + "one_fewer_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + }, + "one_more_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178664.890383 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "no_modeled_irregular_hours": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_fewer_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_more_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "no_modeled_irregular_hours": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "one_fewer_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "one_more_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + }, + "no_modeled_irregular_hours": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606370.953774 + }, + "one_fewer_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + }, + "one_more_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 80, + "weighted_positive_units": 32213.669151621914, + "annual_modeled_benefits": 720606373.2116799 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 249203866.21813098 + }, + "no_modeled_irregular_hours": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 45187.34378196369, + "annual_modeled_benefits": 249203861.9343761 + }, + "one_fewer_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 249203812.3542704 + }, + "one_more_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 254330297.24102315 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "no_modeled_irregular_hours": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_fewer_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_more_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.243193686 + }, + "no_modeled_irregular_hours": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.243193686 + }, + "one_fewer_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329929.363902913 + }, + "one_more_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329927.2470084 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "no_modeled_irregular_hours": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "one_fewer_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "one_more_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "no_modeled_irregular_hours": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.8060375964124, + "annual_modeled_benefits": 18103216.918334827 + }, + "one_fewer_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "one_more_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103302.078379974 + } + } + ], + "production_ready": false, + "interpretation": "assumption stress tests, not confidence intervals; potential modeled benefits, not calibrated spending", + "summary": { + "states_evaluated": 51, + "annual_potential_modeled_benefits": { + "baseline": 2253603947.6976757, + "candidate": 5998228187.26313, + "no_modeled_irregular_hours": 5753893674.6244955, + "one_fewer_day": 5996952419.436973, + "one_more_day": 6019326168.791568 + }, + "sensitivity_flags": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "IL", + "relative_change": -0.29418542010981796, + "absolute_change": -57891644.491547525, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.3599643592223226, + "absolute_change": -43260036.09946096, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + } + ], + "comparisons": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "US", + "relative_change": -0.04073444774199555, + "absolute_change": -244334512.63863468, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AZ", + "relative_change": 3.4795472060447685e-06, + "absolute_change": 80.58466989174485, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CA", + "relative_change": -0.16943514778977237, + "absolute_change": -123012312.16718781, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DC", + "relative_change": -1.4006389962646843e-08, + "absolute_change": -0.33498791977763176, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "FL", + "relative_change": -0.048561659406233436, + "absolute_change": -16350408.75, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IL", + "relative_change": -0.29418542010981796, + "absolute_change": -57891644.491547525, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "LA", + "relative_change": -0.0524188932261243, + "absolute_change": -3818315.91796875, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MN", + "relative_change": -2.262688775133818e-09, + "absolute_change": -0.6219009757041931, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MS", + "relative_change": -1.343320763490555e-07, + "absolute_change": -4.090397037565708, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NM", + "relative_change": -2.9711291489026368e-05, + "absolute_change": -1772.1665807962418, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ND", + "relative_change": -0.011427064224842841, + "absolute_change": -5.382682800292969, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SD", + "relative_change": -4.2273181213440405e-07, + "absolute_change": -2.4557243194431067, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.3599643592223226, + "absolute_change": -43260036.09946096, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VT", + "relative_change": -3.133341646729708e-09, + "absolute_change": -2.257905960083008, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VA", + "relative_change": -1.7189760938327763e-08, + "absolute_change": -4.283754885196686, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WV", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WY", + "relative_change": -4.651262433181563e-06, + "absolute_change": -84.20320442318916, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "US", + "relative_change": -0.00021269077906482196, + "absolute_change": -1275767.82615757, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AZ", + "relative_change": 4.962991992620854e-07, + "absolute_change": 11.494055051356554, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AR", + "relative_change": 0.03494541898564824, + "absolute_change": 1774997.2592508793, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DC", + "relative_change": -4.786141643741066e-10, + "absolute_change": -0.011446915566921234, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "FL", + "relative_change": -0.009060493759575043, + "absolute_change": -3050611.9077730775, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IL", + "relative_change": -6.484094955654378e-07, + "absolute_change": -127.59807059168816, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "KY", + "relative_change": 0.10526150791132716, + "absolute_change": 15.570651608905735, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "LA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MO", + "relative_change": 6.088000082391748e-08, + "absolute_change": 2.6854177340865135, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NM", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ND", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "PA", + "relative_change": -0.000918267365689253, + "absolute_change": -0.5152397716210544, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SD", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VA", + "relative_change": -2.161437597047441e-07, + "absolute_change": -53.863860577344894, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WV", + "relative_change": 1.2011981209133212e-07, + "absolute_change": 1.120709227398038, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "US", + "relative_change": 0.0035173689412546665, + "absolute_change": 21097981.528437614, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AZ", + "relative_change": -1.4454194532278352e-05, + "absolute_change": -334.7523186095059, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DC", + "relative_change": -5.4787842155856995e-09, + "absolute_change": -0.1310349442064762, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "FL", + "relative_change": 0.047437436196758505, + "absolute_change": 15971889.78615272, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KY", + "relative_change": -0.014356727521403111, + "absolute_change": -2.1236975121814794, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "LA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MO", + "relative_change": -5.06508975930975e-08, + "absolute_change": -2.234211839735508, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NM", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ND", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SD", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VA", + "relative_change": 0.020571233908566064, + "absolute_change": 5126431.022892177, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WV", + "relative_change": -1.0677309192568547e-07, + "absolute_change": -0.9961852859705687, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WY", + "relative_change": 5.285448865536073e-08, + "absolute_change": 0.9568407237529755, + "flagged": false + } + ] + } +} diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py index de7a0f347..f7d10a0ee 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_sensitivity.py @@ -5,14 +5,87 @@ import gc import numpy as np +import pandas as pd from microcosm.build.us_runtime.childcare_attendance import ( US_CHILDCARE_ATTENDANCE_COLUMNS, + _ids, + _validate_attendance, ) from microcosm.build.us_runtime.nsece_childcare import NSECEChildcareSource from microcosm.calibrate.geography_constants import US_STATE_NUMERIC_FIPS_TO_POSTAL +def paired_noncalendar_attendance(people, source, alternative): + """Apply an assumption change to the same selected donor, preserving observations. + + Quantile-coupled retransfers can change donor identities when schedules change + sort order. This contrast instead conditions on the realized donor assignment. + It is an assumption diagnostic, not a replacement population or receipt. + """ + columns = list(US_CHILDCARE_ATTENDANCE_COLUMNS) + before = source.children.set_index("donor_id", drop=False).sort_index() + after = alternative.children.set_index("donor_id", drop=False).sort_index() + _ids(before, "donor_id", unique=True) + _ids(after, "donor_id", unique=True) + if not before.index.equals(after.index): + raise ValueError("Paired sensitivity requires the same donor identities.") + # These are the only reconstructed fields the scenario may change. + modeled = [*columns, "ece_hours_per_week", "irregular_hours_per_week"] + pd.testing.assert_frame_equal( + before.drop(columns=modeled, errors="ignore"), + after.drop(columns=modeled, errors="ignore"), + check_exact=True, + ) + fixed = before.attendance_status.ne("summary_bridge") + pd.testing.assert_frame_equal( + before.loc[fixed, columns], after.loc[fixed, columns], check_exact=True + ) + _validate_attendance(before, complete=False) + _validate_attendance(after, complete=False) + _validate_attendance(people, complete=False) + result = ( + people.reindex(columns=columns).to_numpy(dtype=float, na_value=np.nan).copy() + ) + original = result.copy() + labels = people.reindex(columns=[f"{c}_source" for c in columns]).astype("string") + selected = labels.apply(lambda col: col.str.startswith("donor:", na=False)) + donor_ids = labels.where(selected).apply(lambda col: col.str.removeprefix("donor:")) + if donor_ids.nunique(axis=1).gt(1).any(): + raise ValueError("Paired sensitivity found mixed donor lineage within a child.") + row_donor = donor_ids.bfill(axis=1).iloc[:, 0] + uses_donor = selected.any(axis=1).to_numpy() + if (uses_donor & ~people.age.between(0, 12).to_numpy()).any(): + raise ValueError("Paired sensitivity found an out-of-domain donor assignment.") + if ( + row_donor.loc[uses_donor].isna().any() + or not row_donor.loc[uses_donor].isin(before.index).all() + ): + raise ValueError("Paired sensitivity cannot resolve a selected donor.") + expected = before.reindex(row_donor).reindex(columns=columns).to_numpy(dtype=float) + changed = after.reindex(row_donor).reindex(columns=columns).to_numpy(dtype=float) + mask = selected.to_numpy(dtype=bool) + if not np.array_equal(original[mask], expected[mask]): + raise ValueError("Candidate attendance does not match its selected donor.") + # Observed/clone-inherited constraints must remain compatible with the donor. + constrained = uses_donor[:, None] & ~mask & np.isfinite(original) + if not np.array_equal(original[constrained], changed[constrained]): + raise ValueError("Paired scenario conflicts with observed attendance.") + result[mask] = changed[mask] + _validate_attendance(pd.DataFrame(result, columns=columns), complete=False) + modified = np.any(~np.isclose(result, original, equal_nan=True), axis=1) + bridge = row_donor.map(before.attendance_status).eq("summary_bridge").to_numpy() + if (modified & ~bridge).any(): + raise ValueError("Paired scenario changed a measured-calendar assignment.") + return result, { + "donor_assigned_people": int(uses_donor.sum()), + "bridge_assigned_people": int(bridge.sum()), + "changed_people": int(modified.sum()), + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values", + } + + def noncalendar_sensitivity_source(source, *, irregular_hours=True, day_shift=0): """Change only modeled bridge components, keeping measured regular hours. diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py index f152697a0..168742bae 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_qrf.py @@ -8,6 +8,7 @@ from __future__ import annotations import numpy as np +import pandas as pd from scipy.stats import qmc from microcosm.build.us_runtime.childcare_attendance import ( @@ -29,18 +30,53 @@ QRF_CHILDCARE_SCHEDULE_CODE = "childcare_schedule_code" QRF_CHILDCARE_DAY_MULTIPLIER = 25.0 # Daily hours are bounded above by 24. QRF_CHILDCARE_GRID_POWER = 7 # Fixed 128-point two-dimensional Sobol integration. +QRF_INTERVAL_DRAWS = 8 +QRF_INTERVAL_EMPIRICAL_MIX = 0.1 + + +def _interval_rows(children): + return ( + children.age.between(0, 12) + & children.questionnaire_version.eq(1) + & children.attendance_status.isin(["ambiguous_calendar", "partial_calendar"]) + ) class QRFChildcareSchedules: """Canonical weighted QRF with an age-specific empirical schedule decoder.""" - def __init__(self, children, *, seed=915, n_estimators=100): + def __init__( + self, children, *, seed=915, n_estimators=100, allow_interval_training=False + ): if children.attendance_status.eq("summary_bridge").any(): raise ValueError("QRF diagnostics require original measured calendars.") self.training_households = frozenset(children.source_household_id) + self.interval_ids = ( + frozenset(children.loc[_interval_rows(children), "donor_id"]) + if "questionnaire_version" in children + else frozenset() + ) + interval = children.attendance_status.eq("interval_model") + if interval.any() and not allow_interval_training: + raise ValueError( + "Modeled interval rows require explicit experimental opt-in." + ) + if interval.any(): + rows = children.loc[interval] + days = rows.childcare_days_per_week + weekly = days * rows.childcare_hours_per_day + if not ( + days.between(rows.calendar_ece_days_lower, rows.calendar_ece_days_upper) + & weekly.between( + rows.calendar_ece_hours_lower - 1e-10, + rows.calendar_ece_hours_upper + 1e-10, + ) + ).all(): + raise ValueError("Modeled interval schedule violates measured bounds.") pool = ( children.loc[ - children.age.between(0, 12) & children.attendance_status.eq("complete") + children.age.between(0, 12) + & (children.attendance_status.eq("complete") | interval) ] .sort_values("donor_id") .copy() @@ -62,7 +98,22 @@ def __init__(self, children, *, seed=915, n_estimators=100): QRF_CHILDCARE_DAY_MULTIPLIER * pool[days] + pool[hours] ) self.catalogs = {} + self.empirical = {} self.ages = frozenset(pool.age) + for age, group in pool.groupby("age"): + schedules, inverse = np.unique( + np.column_stack( + ( + group[days].gt(0).astype(float), + group[days], + group[days] * group[hours], + ) + ), + axis=0, + return_inverse=True, + ) + masses = np.bincount(inverse, weights=group.child_weight) + self.empirical[age] = (schedules, masses / masses.sum()) for age, group in pool.loc[pool[days] > 0].groupby("age"): group = group.sort_values(QRF_CHILDCARE_SCHEDULE_CODE).drop_duplicates( QRF_CHILDCARE_SCHEDULE_CODE @@ -88,6 +139,10 @@ def prepare(self, children): """Prepare held-out feature profiles in bounded, deterministic batches.""" if self.training_households.intersection(children.source_household_id): raise ValueError("QRF evaluation households overlap training households.") + self._prepare_profiles(children) + + def _prepare_profiles(self, children): + """Internal prediction shared by held-out scoring and training completion.""" profiles = children.reindex(columns=QRF_CHILDCARE_PREDICTORS).drop_duplicates() if not np.isfinite(profiles.to_numpy(dtype=float)).all(): raise ValueError("QRF target predictors must be finite.") @@ -148,3 +203,101 @@ def distribution(self, child): if child.source_household_id in self.training_households: raise ValueError("QRF evaluation households overlap training households.") return self.cache[key] + + +def interval_training_rows(children, model, *, tilt=0): + """One training-only interval completion pass; measured rows remain intact. + + The prior is a fixed QRF/empirical mixture, restricted to observed bounds. + This assumes conditional coarsening at random. Tilts expose sensitivity to + that assumption; completed rows must never enter observed-outcome scoring. + """ + if tilt not in (-1, 0, 1): + raise ValueError("Interval sensitivity tilt must be -1, 0 or 1.") + target = children.loc[_interval_rows(children)].copy() + bounds = target.reindex( + columns=[ + "calendar_ece_days_lower", + "calendar_ece_days_upper", + "calendar_ece_hours_lower", + "calendar_ece_hours_upper", + ] + ).to_numpy(dtype=float) + if ( + not np.isfinite(bounds).all() + or (bounds < 0).any() + or (bounds[:, :2] > 7).any() + or (bounds[:, 2:] > 168).any() + or (bounds[:, 0] > bounds[:, 1]).any() + or (bounds[:, 2] > bounds[:, 3]).any() + ): + raise ValueError("Training calendar bounds must be finite and ordered.") + if not set(target.donor_id).issubset(model.interval_ids): + raise ValueError("Interval completion includes children outside training.") + if not set(children.source_household_id).issubset(model.training_households): + raise ValueError("Interval completion includes households outside training.") + model._prepare_profiles(target) + rows, completed, unsupported = [], set(), [] + quantiles = (np.arange(QRF_INTERVAL_DRAWS) + 0.5) / QRF_INTERVAL_DRAWS + for _, child in target.iterrows(): + key = tuple(child.reindex(QRF_CHILDCARE_PREDICTORS)) + values, _, probability = model.cache[key] + empirical, mass = model.empirical[child.age] + combined, inverse = np.unique( + np.concatenate([values, empirical]), axis=0, return_inverse=True + ) + probability = np.bincount( + inverse, + weights=np.concatenate( + [ + (1 - QRF_INTERVAL_EMPIRICAL_MIX) * probability, + QRF_INTERVAL_EMPIRICAL_MIX * mass, + ] + ), + ) + allowed = ( + (combined[:, 1] >= child.calendar_ece_days_lower) + & (combined[:, 1] <= child.calendar_ece_days_upper) + & (combined[:, 2] >= child.calendar_ece_hours_lower - 1e-10) + & (combined[:, 2] <= child.calendar_ece_hours_upper + 1e-10) + ) + if not allowed.any(): + unsupported.append(float(child.child_weight)) + continue + schedules = combined[allowed] + probability = probability[allowed] * np.exp(tilt * schedules[:, 2] / 40) + probability /= probability.sum() + cumulative = probability.cumsum() + cumulative[-1] = 1 + chosen = schedules[np.searchsorted(cumulative, quantiles, side="right")] + for draw, (_, days, weekly) in enumerate(chosen): + row = child.copy() + row["donor_id"] = f"{child.donor_id}:interval:{draw}" + row["attendance_status"] = "interval_model" + row["child_weight"] = child.child_weight / QRF_INTERVAL_DRAWS + row["childcare_attending_days_per_month"] = np.floor(days * 52 / 12 + 0.5) + row["childcare_days_per_week"] = days + row["childcare_hours_per_day"] = weekly / days if days else 0.0 + row["ece_hours_per_week"] = weekly + rows.append(row) + completed.add(child.donor_id) + retained = children.loc[~children.donor_id.isin(completed)] + result = pd.concat([retained, pd.DataFrame(rows)], ignore_index=True) + if not np.isclose( + result.child_weight.sum(), children.child_weight.sum(), rtol=1e-12 + ): + raise ValueError("Interval completion changed design-weight mass.") + return result, { + "tilt": tilt, + "incomplete_training_children": len(target), + "completed_training_children": len(completed), + "unsupported_training_children": len(unsupported), + "unsupported_child_weight": float(sum(unsupported)), + "completed_child_weight": float( + target.loc[target.donor_id.isin(completed), "child_weight"].sum() + ), + "draws_per_child": QRF_INTERVAL_DRAWS, + "empirical_mixture_weight": QRF_INTERVAL_EMPIRICAL_MIX, + "design_weight_conserved": True, + "interpretation": "modeled training rows, not observations; conditional coarsening assumption", + } diff --git a/packages/microcosm-build/tests/test_us_childcare_qrf.py b/packages/microcosm-build/tests/test_us_childcare_qrf.py index c44515623..8517bbace 100644 --- a/packages/microcosm-build/tests/test_us_childcare_qrf.py +++ b/packages/microcosm-build/tests/test_us_childcare_qrf.py @@ -113,3 +113,86 @@ def test_real_canonical_model_gives_valid_row_order_invariant_schedules(): assert set(map(tuple, first[0])).issubset( {(0.0, 0.0, 0.0), (1.0, 1.0, 1.0), (1.0, 2.0, 4.0)} ) + + +def _interval_fixture(): + rows = _rows().assign(questionnaire_version=1) + rows["calendar_ece_days_lower"] = rows[DAYS] + rows["calendar_ece_days_upper"] = rows[DAYS] + rows["calendar_ece_hours_lower"] = rows[DAYS] * rows[HOURS] + rows["calendar_ece_hours_upper"] = rows[DAYS] * rows[HOURS] + missing = ( + rows.iloc[[0]] + .copy() + .assign( + donor_id="u", + source_household_id="u", + attendance_status="partial_calendar", + child_weight=8.0, + calendar_ece_days_lower=1, + calendar_ece_days_upper=2, + calendar_ece_hours_lower=4.0, + calendar_ece_hours_upper=5.0, + ) + ) + missing[[MONTH, DAYS, HOURS]] = np.nan + return pd.concat([rows, missing], ignore_index=True) + + +def test_interval_completion_preserves_bounds_weights_and_measured_rows(monkeypatch): + monkeypatch.setattr(module, "fit", lambda *a, **k: _FakeFit()) + rows = _interval_fixture() + model = module.QRFChildcareSchedules(rows) + for tilt in (-1, 0, 1): + result, audit = module.interval_training_rows(rows, model, tilt=tilt) + pd.testing.assert_frame_equal(result.iloc[:4][rows.columns], rows.iloc[:4]) + modeled = result.loc[result.attendance_status.eq("interval_model")] + assert len(modeled) == module.QRF_INTERVAL_DRAWS + assert modeled.child_weight.sum() == 8 + assert (modeled[DAYS] == 2).all() + assert (modeled[HOURS] == 2).all() + assert audit["completed_training_children"] == 1 + assert audit["design_weight_conserved"] + with pytest.raises(ValueError, match="opt-in"): + module.QRFChildcareSchedules(result) + module.QRFChildcareSchedules(result, allow_interval_training=True) + + +def test_interval_completion_reports_unsupported_intervals_without_clipping( + monkeypatch, +): + monkeypatch.setattr(module, "fit", lambda *a, **k: _FakeFit()) + rows = _interval_fixture() + rows.loc[4, ["calendar_ece_hours_lower", "calendar_ece_hours_upper"]] = [100, 101] + model = module.QRFChildcareSchedules(rows) + result, audit = module.interval_training_rows(rows, model) + pd.testing.assert_frame_equal(result, rows) + assert audit["unsupported_training_children"] == 1 + assert audit["unsupported_child_weight"] == 8 + + +def test_interval_completion_cannot_import_heldout_households(monkeypatch): + monkeypatch.setattr(module, "fit", lambda *a, **k: _FakeFit()) + rows = _interval_fixture() + model = module.QRFChildcareSchedules(rows) + heldout = rows.copy() + heldout.loc[4, "source_household_id"] = "heldout" + with pytest.raises(ValueError, match="outside training"): + module.interval_training_rows(heldout, model) + heldout.loc[4, "donor_id"] = "heldout-child" + with pytest.raises(ValueError, match="outside training"): + module.interval_training_rows(heldout, model) + + +def test_interval_completion_rejects_invalid_bounds_and_false_observations(monkeypatch): + monkeypatch.setattr(module, "fit", lambda *a, **k: _FakeFit()) + rows = _interval_fixture() + model = module.QRFChildcareSchedules(rows) + bad = rows.copy() + bad.loc[4, "calendar_ece_hours_upper"] = np.nan + with pytest.raises(ValueError, match="finite and ordered"): + module.interval_training_rows(bad, model) + result, _ = module.interval_training_rows(rows, model) + result.loc[result.attendance_status.eq("interval_model"), HOURS] = 20 + with pytest.raises(ValueError, match="violates measured bounds"): + module.QRFChildcareSchedules(result, allow_interval_training=True) diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index 0b3b5d235..7f5103403 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -27,6 +27,7 @@ NSECE_CALENDAR_BLOCKS, NSECE_CHILD_INDICES, NSECE_PROVIDER_INDICES, + NSECEChildcareSource, assert_childcare_attendance_exportable, derive_nsece_childcare, load_nsece_childcare, @@ -1195,3 +1196,109 @@ def test_schedule_sensitivity_preserves_measured_regular_hours( _source(), irregular_hours=False, day_shift=-1 ) assert_frame_equal(observed.children, _source().children) + + +def _paired_sensitivity_fixture(): + source = _source() + source.children["attendance_status"] = "summary_bridge" + source.children["regular_hours_per_week"] = 12.0 + source.children["irregular_hours_per_week"] = 1.0 + source.children[HOURS] = 13 / source.children[DAYS] + source.children["ece_hours_per_week"] = 13.0 + child = source.children.iloc[0] + people = pd.DataFrame( + {"age": [child.age], **{c: [child[c]] for c in (MONTH, DAYS, HOURS)}} + ) + for c in (MONTH, DAYS, HOURS): + people[f"{c}_source"] = f"donor:{child.donor_id}" + return people, source + + +def test_paired_sensitivity_keeps_selected_donor_and_measured_hours(): + from microcosm.build.us_runtime.childcare_sensitivity import ( + noncalendar_sensitivity_source, + paired_noncalendar_attendance, + ) + + people, source = _paired_sensitivity_fixture() + original = people.copy(deep=True) + changed = noncalendar_sensitivity_source(source, day_shift=-1) + values, report = paired_noncalendar_attendance(people, source, changed) + assert values[0, 1] == people[DAYS].iloc[0] - 1 + assert values[0, 1] * values[0, 2] == pytest.approx(13) + assert report["changed_people"] == 1 + assert report["measured_calendar_assignments_changed"] == 0 + assert_frame_equal(people, original) + # Row order in the donor source has no effect on the identity lookup. + reversed_source = NSECEChildcareSource( + changed.children.iloc[::-1], changed.weights, changed.source_receipt + ) + np.testing.assert_array_equal( + values, paired_noncalendar_attendance(people, source, reversed_source)[0] + ) + + +@pytest.mark.parametrize( + "problem", ["unknown_donor", "mixed_donor", "wrong_value", "observed_conflict"] +) +def test_paired_sensitivity_rejects_invalid_lineage_and_observation_conflicts(problem): + from microcosm.build.us_runtime.childcare_sensitivity import ( + noncalendar_sensitivity_source, + paired_noncalendar_attendance, + ) + + people, source = _paired_sensitivity_fixture() + changed = noncalendar_sensitivity_source(source, day_shift=-1) + if problem == "unknown_donor": + for c in (MONTH, DAYS, HOURS): + people[f"{c}_source"] = "donor:missing" + elif problem == "mixed_donor": + people[f"{HOURS}_source"] = "donor:another" + elif problem == "wrong_value": + people[HOURS] += 0.1 + else: + people[f"{DAYS}_source"] = "observed" + with pytest.raises(ValueError): + paired_noncalendar_attendance(people, source, changed) + + +def test_paired_sensitivity_does_not_reassign_measured_donors_when_care_order_changes(): + from microcosm.build.us_runtime.childcare_sensitivity import ( + noncalendar_sensitivity_source, + paired_noncalendar_attendance, + ) + from microcosm.frame import WeightKind, Weights + + people, source = _paired_sensitivity_fixture() + measured = source.children.copy().assign( + donor_id="measured", + attendance_status="complete", + regular_hours_per_week=5.0, + irregular_hours_per_week=0.0, + ece_hours_per_week=5.0, + ) + measured[HOURS] = 1.0 + combined = NSECEChildcareSource( + pd.concat([source.children, measured], ignore_index=True), + Weights(np.array([1.0, 1.0]), WeightKind.DESIGN), + source.source_receipt, + ) + observed_recipient = people.copy() + observed_recipient[HOURS] = 1.0 + for c in (MONTH, DAYS, HOURS): + observed_recipient[f"{c}_source"] = "donor:measured" + outside = people.copy() + outside["age"] = 30 + for c in (MONTH, DAYS, HOURS): + outside[c] = 0.0 + outside[f"{c}_source"] = "inherited_engine_baseline" + people = pd.concat([people, observed_recipient, outside], ignore_index=True) + alternative = noncalendar_sensitivity_source(combined, day_shift=-1) + before_order = combined.children.sort_values([DAYS, HOURS]).donor_id.tolist() + after_order = alternative.children.sort_values([DAYS, HOURS]).donor_id.tolist() + assert before_order == after_order[::-1] + result, audit = paired_noncalendar_attendance(people, combined, alternative) + assert result[0, 1] == 4 + np.testing.assert_array_equal(result[1:], people.iloc[1:][[MONTH, DAYS, HOURS]]) + assert audit["changed_people"] == 1 + assert audit["bridge_assigned_people"] == 1 diff --git a/tools/validate_us_childcare_intervals.py b/tools/validate_us_childcare_intervals.py new file mode 100644 index 000000000..125d0845f --- /dev/null +++ b/tools/validate_us_childcare_intervals.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Evaluate declared interval-conditioned training and both assumption tilts.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + +import numpy as np +import validate_us_childcare_qrf as qrf_tool + +from microcosm.build.us_runtime import ( + nsece_childcare_pooling, + nsece_childcare_qrf, + nsece_childcare_sibling_validation, +) +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + attendance_recipe_identity, +) +from microcosm.build.us_runtime.nsece_childcare_qrf import ( + QRFChildcareSchedules, + interval_training_rows, +) +from microcosm.build.us_runtime.nsece_childcare_sibling_validation import ( + _household_splits, + _observed_child_summary, +) + + +def _records(model, target, complete): + model.prepare(target) + for _, child in target.iterrows(): + values, _, probability = model.distribution(child) + yield ( + float(child.child_weight), + min(int(child.childcare_household_size), 3), + bool(complete.loc[child.source_household_id]), + np.array( + [ + float(child.childcare_days_per_week > 0), + child.childcare_days_per_week, + child.ece_hours_per_week, + ] + ), + np.average(values, axis=0, weights=probability), + np.average(values**2, axis=0, weights=probability), + ) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("Report path must be new") + source, children = qrf_tool.load_source_children( + args.household_tsv, args.calendar_tsv + ) + records = { + name: [] + for name in ("complete_only", "interval", "lower_hours", "higher_hours") + } + folds = [] + for fold, (train_mask, target_mask) in enumerate( + _household_splits(children, seed=271828, validation_seed=None, partition="all") + ): + train, target = children.loc[train_mask], children.loc[target_mask] + complete = target.groupby("source_household_id").attendance_status.agg( + lambda x: x.eq("complete").all() + ) + observed = target.loc[target.attendance_status.eq("complete")] + base = QRFChildcareSchedules(train) + records["complete_only"].extend(_records(base, observed, complete)) + detail = { + "fold": fold, + "evaluated_children": len(observed), + "household_overlap": 0, + "completions": {}, + } + for name, tilt in (("interval", 0), ("lower_hours", -1), ("higher_hours", 1)): + augmented, audit = interval_training_rows(train, base, tilt=tilt) + model = QRFChildcareSchedules(augmented, allow_interval_training=True) + records[name].extend(_records(model, observed, complete)) + detail["completions"][name] = audit + print("completed fold", fold, name, flush=True) + folds.append(detail) + repo = Path(__file__).resolve().parents[1] + plan = repo / "experiments/us-childcare-attendance/interval-and-paired-plan.txt" + paths = [ + Path(__file__), + Path(qrf_tool.__file__), + Path(nsece_childcare_qrf.__file__), + Path(nsece_childcare_pooling.__file__), + Path(nsece_childcare_sibling_validation.__file__), + ] + report = { + "source": source.source_receipt, + "recipe": attendance_recipe_identity(), + "plan_sha256": hashlib.sha256(plan.read_bytes()).hexdigest(), + "diagnostic_code_sha256": { + p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in paths + }, + "folds": folds, + "models": { + name: _observed_child_summary(rows) for name, rows in records.items() + }, + "production_ready": False, + "interpretation": "development comparison; completed training intervals remain modeled under declared coarsening assumptions; no held-out household contributes to completion or refitting", + } + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + for name, result in report["models"].items(): + print( + name, + "failed child screens", + sum(not check["passed"] for check in result["screen"]["checks"]), + flush=True, + ) + + +if __name__ == "__main__": + main() diff --git a/tools/validate_us_childcare_qrf.py b/tools/validate_us_childcare_qrf.py index 04677da63..5398fe837 100644 --- a/tools/validate_us_childcare_qrf.py +++ b/tools/validate_us_childcare_qrf.py @@ -29,17 +29,11 @@ ) -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--household-tsv", type=Path, required=True) - parser.add_argument("--calendar-tsv", type=Path, required=True) - parser.add_argument("--report", type=Path, required=True) - args = parser.parse_args() - if args.report.exists(): - parser.error("Report path must be new") - source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) +def load_source_children(household_tsv, calendar_tsv): + """Verify source pins and add the declared common household predictors.""" + source = load_nsece_childcare(household_tsv, calendar_tsv) raw = pd.read_csv( - args.household_tsv, + household_tsv, sep="\t", usecols=["HH4_METH_CASEID", "HH4_HHCOMP_MEMBERS", "HH4_HHCOMP_NUMPARENTS"], ) @@ -53,6 +47,18 @@ def main(): children["resident_parent_count"] = children.source_household_id.map( raw.HH4_HHCOMP_NUMPARENTS ) + return source, children + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("Report path must be new") + source, children = load_source_children(args.household_tsv, args.calendar_tsv) records, folds, movements = [], [], [] for fold, (train_mask, target_mask) in enumerate( _household_splits(children, seed=271828, validation_seed=None, partition="all") diff --git a/tools/validate_us_childcare_sensitivity.py b/tools/validate_us_childcare_sensitivity.py index 753bcd608..3a69e8aff 100644 --- a/tools/validate_us_childcare_sensitivity.py +++ b/tools/validate_us_childcare_sensitivity.py @@ -14,11 +14,11 @@ from pathlib import Path import numpy as np +import pandas as pd -from microcosm.build.us_runtime.childcare_attendance import ( - US_CHILDCARE_ATTENDANCE_COLUMNS, -) +from microcosm.build.frame_checkpoint import load_frame_checkpoint from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, attendance_recipe_identity, ) from microcosm.build.us_runtime.childcare_population import ( @@ -27,6 +27,7 @@ from microcosm.build.us_runtime.childcare_sensitivity import ( compare_childcare_scenarios, noncalendar_sensitivity_source, + paired_noncalendar_attendance, ) from microcosm.build.us_runtime.h5_io import load_legacy_calibrated_us_h5 from microcosm.build.us_runtime.nsece_childcare import ( @@ -41,6 +42,7 @@ from microcosm.build.us_runtime.nsece_childcare_dependence import ( fit_nsece_sibling_dependence, ) +from microcosm.frame import Frame def _sha256(path): @@ -58,15 +60,51 @@ def main(): parser.add_argument("--seed", type=int, default=915) parser.add_argument("--year", type=int, default=2026) parser.add_argument("--report", type=Path, required=True) + parser.add_argument( + "--candidate-checkpoint", + type=Path, + help="Reuse a bound candidate from this parent/seed; retain its donor assignments", + ) args = parser.parse_args() if args.report.exists(): parser.error("The report path must be new") if _sha256(args.parent_h5) != args.parent_sha256: parser.error("Parent population hash mismatch") parent = load_legacy_calibrated_us_h5(args.parent_h5) - normalized = harmonize_asec_childcare_predictors( - parent, source_cache=args.asec_source_cache - ) + if args.candidate_checkpoint: + stored = load_frame_checkpoint(args.candidate_checkpoint) + if stored.metadata["parent_checkpoint_sha256"] != args.parent_sha256: + parser.error("Candidate checkpoint belongs to another parent") + frame = stored.frame + transferred = Frame( + {e: frame.table(e) for e in frame.entities}, + frame.schema, + {e: frame.weights_for(e) for e in frame.weighted_entities}, + frame.strata, + mass_log=frame.mass_log, + metadata=stored.metadata["frame_metadata"], + ) + assert_bound_childcare_attendance(transferred) + if transferred.metadata["childcare_attendance_stage"]["seed"] != args.seed: + parser.error("Candidate checkpoint uses another seed") + for entity in parent.entities: + # Check values, missingness and IDs; the dedicated artifact verifier + # additionally checks dtypes after normalizing string storage. + pd.testing.assert_frame_equal( + parent.table(entity), + transferred.table(entity).reindex(columns=parent.table(entity).columns), + check_dtype=False, + check_exact=True, + ) + for entity in parent.weighted_entities: + np.testing.assert_array_equal( + parent.weights_for(entity).values, + transferred.weights_for(entity).values, + ) + else: + normalized = harmonize_asec_childcare_predictors( + parent, source_cache=args.asec_source_cache + ) source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) dependence = fit_nsece_sibling_dependence(source.children) bridged = bridge_nsece_noncalendar_attendance(source, seed=args.seed) @@ -78,27 +116,33 @@ def main(): "one_fewer_day": noncalendar_sensitivity_source(bridged, day_shift=-1), "one_more_day": noncalendar_sensitivity_source(bridged, day_shift=1), } - scenarios = {} - for name, donors in arms.items(): + if not args.candidate_checkpoint: transferred = with_us_nsece_childcare_attendance( normalized, - donors, + bridged, seed=args.seed, match_columns=NSECE_CHILDCARE_MATCH_COLUMNS, fallback_match_columns=NSECE_CHILDCARE_FALLBACK_COLUMNS, sibling_dependence=dependence["rho"], ) - scenarios[name] = transferred.table("person")[ - list(US_CHILDCARE_ATTENDANCE_COLUMNS) - ].to_numpy(dtype=float) + scenarios, assignment_checks = {}, {} + for name, donors in arms.items(): + scenarios[name], assignment_checks[name] = paired_noncalendar_attendance( + transferred.table("person"), bridged, donors + ) print(f"Prepared {name}", flush=True) - people = normalized.table("person") + people = transferred.table("person") young = people.age.between(0, 12).to_numpy() report = { "parent_sha256": args.parent_sha256, "source": source.source_receipt, "recipe": attendance_recipe_identity(), "seed": args.seed, + "candidate_checkpoint_sha256": _sha256(args.candidate_checkpoint) + if args.candidate_checkpoint + else None, + "assignment_checks": assignment_checks, + "coupling": "same donor identities across scenarios; no care-rank reassignment", "engine_version": version("policyengine-us"), "policy_year": args.year, "code_sha256": { From 4b567b260ea7e21f4d7b97c38dc54bb74b56bcc9 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Thu, 17 Sep 2026 13:52:34 -0400 Subject: [PATCH 11/16] Harden attendance receipt, builder wiring and NSECE bridge after review - Keep frame metadata through the ACA source-output step so the attendance receipt reaches the final export check. - Add the native receipt key without rewriting the person table, and persist and restore only the attendance context and binding. - Compare recipe code/runtime identity at bind and release export only; read-only native ingress checks content. - Carry the receipt through the L0 refit export and restore it in the fiscal builder's --base-h5 loader. - Refuse a build with neither NSECE source files nor bound attendance before calibration, and keep an unbound-attendance failure in the batched report. - Widen thin noncalendar-bridge cells to the next matching level and count children by matching level. - Reject unlisted region, parent-work and negative income codes (User's Guide HH-63, HH-175, HH-483). - Fail the require_observed outside-domain policy early with the fixing flag, and run the stage after the hours producer. Co-Authored-By: Claude Fable 5.1 --- CLAUDE.md | 8 +++ README.md | 7 ++ .../915-us-childcare-attendance.added.md | 1 + docs/us-childcare-attendance.md | 33 +++++++-- .../childcare_attendance_receipt.py | 24 ++++++- .../us_runtime/childcare_attendance_stage.py | 31 +++++++-- .../build/us_runtime/l0_refit_export.py | 9 +++ .../build/us_runtime/nsece_childcare.py | 17 +++++ .../us_runtime/nsece_childcare_bridge.py | 26 +++++-- .../tests/test_frame_serializer_registry.py | 10 +-- .../tests/test_us_fiscal_refresh_builder.py | 15 ++++ .../tests/test_us_nsece_childcare.py | 61 +++++++++++++++- tools/build_us_fiscal_refresh_release.py | 69 ++++++++++++++----- tools/prepare_us_childcare_attendance.py | 4 +- 14 files changed, 270 insertions(+), 45 deletions(-) create mode 100644 changelog.d/915-us-childcare-attendance.added.md diff --git a/CLAUDE.md b/CLAUDE.md index 41490df9e..aa9e5b18d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -102,6 +102,14 @@ build recorded staging telemetry that never reached its repo publishes without the flag. Never publish or promote artifacts as a side effect of another task. +US fiscal builds require the NSECE childcare-attendance stage +(`--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, +`--childcare-attendance-inherit-outside-domain-baseline`): attendance is a +required, non-waivable export input bound by a per-person receipt, a build +without it is refused before calibration, and +`tools/build_us_exact_k_ladder_release.py` cannot yet pass these flags. See +[the attendance runbook](docs/us-childcare-attendance.md). + The US native-SPM-role source-enrichment lane is a separate release type: `tools/build_us_spm_role_enrichment.py` creates a local candidate from the exact reviewed BuildP parent, preserving original variables and inherited schema-5 diff --git a/README.md b/README.md index 12c9c1bde..05ef35b01 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ This writes `progress.json`, `events.ndjson`, `calibration_progress.json`, and final candidate diagnostics under `runs//` without updating production `latest.json`. +The three childcare-attendance inputs are required export inputs. A US fiscal +build also needs the licensed NSECE files +(`--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv` +and `--childcare-attendance-inherit-outside-domain-baseline`); without them the +build is refused before calibration. See +[docs/us-childcare-attendance.md](docs/us-childcare-attendance.md). + See [SYSTEM_REQUIREMENTS.md](SYSTEM_REQUIREMENTS.md) for the measured memory, disk, and CPU footprint of developing and building locally (and what to budget on a build machine — RAM is the binding constraint). diff --git a/changelog.d/915-us-childcare-attendance.added.md b/changelog.d/915-us-childcare-attendance.added.md new file mode 100644 index 000000000..595f880fe --- /dev/null +++ b/changelog.d/915-us-childcare-attendance.added.md @@ -0,0 +1 @@ +Derive the three PolicyEngine-US childcare attendance inputs (`childcare_attending_days_per_month`, `childcare_days_per_week`, `childcare_hours_per_day`) for children ages 0–12 from the licensed 2024 NSECE household and calendar files and transfer them onto the US fiscal refresh population (#915). `tools/build_us_fiscal_refresh_release.py` gains `--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, `--childcare-attendance-asec-cache` and `--childcare-attendance-inherit-outside-domain-baseline`; the three inputs become required release inputs bound to a per-person receipt that the native loaders and the builder's `--base-h5` loader check, that the L0 refit export carries forward, and that no coverage override can waive. A build with neither the source files nor bound attendance is refused before calibration. PolicyEngine-US defaults are unchanged and no population is published. diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index f14280b34..d3146a8fc 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -51,7 +51,7 @@ source pins, income bands, and price-year conventions. | `HH4_TYPEOFCARE_AGG_X_Y` | Child/provider care type | | `HH4_RPARENT` | Whether respondent care is parental care | | `HH4_REGION` | Census region | -| `HH4_PARWORK_STATUS` | Work status of parents of any under-13 household child | +| `HH4_PARWORK_STATUS` | Work status of parents of any under-13 household child; codes -1, 0, 1, 2 | | `HH4_METH_QUEXVERSION` | Main, summer/typical-May, or new-school-year instrument | | `HH4_ECON_INCOME_ANNUAL` | Published household pretax income for 2023 | | `HHC4_NPC_HRSWEEK_TOC1..5_X` | Regular-care weekly hours for the noncalendar bridge | @@ -84,7 +84,21 @@ bridge preserves their published regular weekly hours and borrows days and irregular hours jointly from ten nearest complete-calendar donors (including all distance ties), using log regular hours, matching covariates, and survey weights. Regular-care participation must agree. Zero regular hours does not establish zero irregular -care. Bridged rows are labeled `summary_bridge`, never `complete`. +care. Bridged rows are labeled `summary_bridge`, never `complete`. A cell with +fewer than ten donors widens to the next matching level, so a thin cell cannot +keep donors however distant their regular hours; the receipt counts children by +matching level. + +The loader accepts only regions 1-4, parent-work codes -1, 0, 1 and 2, and +non-negative income; any other value, including a negative reserve code, fails +instead of becoming a matching cell. The pinned files hold no other values. +`HH4_PARWORK_STATUS` (User's Guide HH-483) records whether all, some or no +parents of any under-13 household child attended work in the week before the +interview, and its -1 is "No parents". The ASEC side uses the same last-week +concept and the same -1 ("no resident parent of an under-13 child"), so an +employed parent who was absent that week counts as not working in both sources. +`HH4_ECON_INCOME_ANNUAL` (HH-175) is imputed where unreported, top coded, and +has a minimum of 0, so the lowest income band holds reported or imputed zeros. Default matching uses age, Census region, parent work, and household income band. The declared sparse-cell hierarchy drops region, then income, then parent work; @@ -183,8 +197,11 @@ Code hashes and environment versions accompany the aggregate preparation report. The receipt binds the source hashes, contract, recipe code, runtime versions, seed, matching/bridge settings, fitted dependence, and outside-domain policy to each person's ID, household link, age, and three attendance values. Both native -US loaders restore and check it. A missing, stale, or altered receipt fails; -changing seed or settings requires rebuilding from the original parent. An +US loaders and the fiscal builder's `--base-h5` loader restore and check it. A +missing or altered receipt fails at load. Recipe code and runtime versions are +compared when a stage binds and when the fiscal build exports, not at read-only +load, so a released file stays readable as a reference after a dependency bump. +Changing seed or settings requires rebuilding from the original parent. An identical rerun verifies and reuses the existing values. A production-stage input with existing under-13 attendance and no production receipt is rejected, including all-zero columns; use the original unmodified parent. The lower-level @@ -196,7 +213,9 @@ execution. The final fiscal export checks every row for completeness, bounds, integral monthly days, coherent zero schedules, and its source binding before writing. Generic coverage overrides cannot waive this check. The written native file receives the receipt and is reloaded and checked before source evidence is -reported. Private per-person hashes stay in local checkpoints/H5; public reports +reported. The receipt key holds only the attendance context and binding, never +other frame metadata, and adding it does not rewrite any entity table. The L0 +refit export carries the receipt forward. Private per-person hashes stay in local checkpoints/H5; public reports contain only aggregate receipt summaries. The private inventory is a sequence of ID/hash pairs: population-sized dictionaries cause quadratic traversal in the Frame metadata container, which is intended for small mappings. These hashes detect accidental stale @@ -211,7 +230,9 @@ continues to describe the earlier pool simulation; attendance is supplied by thi subsequent fiscal-build stage and enforced by the final release input contract. Existing release input gates still apply; a build without required attendance inputs cannot substitute -an engine default for a persisted input. +an engine default for a persisted input. A fiscal build given neither the TSV +flags nor bound attendance is refused before calibration, and the +exact-k ladder wrapper cannot yet pass these flags. Unknown values outside ages 0–12 stay null in the source model. The explicit outside-domain export policy fills only these missing cells with the pinned diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py index 6d861e764..be67e3c7c 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_receipt.py @@ -158,12 +158,17 @@ def assert_bound_childcare_attendance(frame, *, require_stage=True): execution = binding["execution"] if binding["execution_sha256"] != _digest(execution): raise ValueError("Attendance execution hash mismatch.") - if execution["recipe"] != attendance_recipe_identity(): - raise ValueError("Attendance recipe changed; rebuild from the original parent.") if execution["context"] != _context(frame): raise ValueError("Attendance metadata disagrees with its bound execution.") context = execution["context"] if require_stage: + # Code/runtime identity gates binding and release export. Read-only + # ingress checks content only, so a released H5 stays loadable after a + # dependency bump or an edit to the recipe modules. + if execution["recipe"] != attendance_recipe_identity(): + raise ValueError( + "Attendance recipe changed; rebuild from the original parent." + ) stage = context.get("childcare_attendance_stage", {}) source = context.get("nsece_childcare_attendance", {}) expected = [x["sha256"] for x in childcare_attendance_contract()["artifacts"]] @@ -198,11 +203,24 @@ def assert_bound_childcare_attendance(frame, *, require_stage=True): } +def attendance_receipt_payload(metadata): + """Only the attendance context and binding; never unrelated frame metadata.""" + keys = (*ATTENDANCE_CONTEXT_KEYS, ATTENDANCE_RECEIPT_KEY) + return _plain({key: metadata[key] for key in keys if key in metadata}) + + +def write_native_childcare_receipt(path, metadata): + """Append the receipt key to a written native H5; entity tables are untouched.""" + payload = json.dumps(attendance_receipt_payload(metadata), allow_nan=False) + with pd.HDFStore(path, mode="a") as store: + store.put(ATTENDANCE_H5_KEY, pd.Series([payload])) + + def restore_native_childcare_receipt(path, frame): """Load the receipt through either native ingress, preserving its validation.""" with pd.HDFStore(path, mode="r") as store: metadata = ( - json.loads(store[ATTENDANCE_H5_KEY].iloc[0]) + attendance_receipt_payload(json.loads(store[ATTENDANCE_H5_KEY].iloc[0])) if ATTENDANCE_H5_KEY in store else {} ) diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py index 8e1dbc94a..adb6a3970 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/childcare_attendance_stage.py @@ -1,7 +1,8 @@ """Build-stage orchestration for the licensed NSECE attendance source extension. The existing generation-0 source manifest is byte-frozen. This extension has a -separate packaged SourceStageSpec and runs after relationship/hours producers. +separate packaged SourceStageSpec and runs after the childcare-expense and hours +producers (the harmonizer reads raw parent pointers and last-week hours). It is also callable on an exact native parent for candidate qualification. """ @@ -24,7 +25,9 @@ from microcosm.build.us_runtime.childcare_attendance_receipt import ( ATTENDANCE_H5_KEY, assert_bound_childcare_attendance, + attendance_receipt_payload, bind_childcare_attendance, + write_native_childcare_receipt, ) from microcosm.build.us_runtime.childcare_population import ( harmonize_asec_childcare_predictors, @@ -140,6 +143,22 @@ def with_us_childcare_attendance_inputs( raise ValueError( "Pre-existing child attendance lacks a production receipt; rebuild from the original parent." ) + outside = people.loc[~people.age.between(0, 12)] + if ( + not inherit_outside_domain_baseline + and len(outside) + and ( + len(existing) < len(US_CHILDCARE_ATTENDANCE_COLUMNS) + or outside[existing].isna().any().any() + ) + ): + # Fail before the bridge and donor draw: only ages 0-12 are modeled. + raise ValueError( + "Persons outside ages 0-12 have no observed attendance and the " + "require_observed policy cannot export them; pass " + "inherit_outside_domain_baseline=True " + "(--childcare-attendance-inherit-outside-domain-baseline)." + ) dependence = fit_nsece_sibling_dependence(source.children) source = bridge_nsece_noncalendar_attendance(source, seed=seed) normalized = harmonize_asec_childcare_predictors( @@ -276,14 +295,12 @@ def _write_childcare_candidate_person_table( The caller owns the temporary copy and verifies the native dataset reload. Keep nullable booleans and their masks intact if the person table has them. """ + payload = json.dumps(attendance_receipt_payload(receipt), allow_nan=False) with pd.HDFStore(path, mode="a") as store: put_frame_table( store, "person", people, preferred_format="table", data_columns=True ) - store.put( - ATTENDANCE_H5_KEY, - pd.Series([json.dumps(receipt, default=dict, allow_nan=False)]), - ) + store.put(ATTENDANCE_H5_KEY, pd.Series([payload])) def persist_native_childcare_receipt(path: str | Path, frame: Frame) -> dict: @@ -296,8 +313,8 @@ def persist_native_childcare_receipt(path: str | Path, frame: Frame) -> dict: assert_childcare_attendance_exportable(frame) summary = assert_bound_childcare_attendance(frame) - dataset = USSingleYearDataset(file_path=str(path)) - _write_childcare_candidate_person_table(Path(path), dataset.person, frame.metadata) + # The engine already wrote every entity table; only the receipt key is added. + write_native_childcare_receipt(path, frame.metadata) # Use the written values, not the pre-export arrays, for the binding check. dataset = USSingleYearDataset(file_path=str(path)) tables = {e: getattr(dataset, e).copy() for e in frame.entities} diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py b/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py index 0aa782794..e9bb41fbb 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/l0_refit_export.py @@ -622,6 +622,15 @@ def export_us_l0_refit_h5( destination = Path(output_h5) destination.parent.mkdir(parents=True, exist_ok=True) PolicyEngineUSEngine().write_dataset(export_frame, destination, period=period) + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + ATTENDANCE_RECEIPT_KEY, + write_native_childcare_receipt, + ) + + if ATTENDANCE_RECEIPT_KEY in export_frame.metadata: + # The selection keeps each retained person's bound attendance; without + # the receipt key the native loaders refuse this export. + write_native_childcare_receipt(destination, export_frame.metadata) copied_attrs = copy_microcosm_root_attrs(root_attrs_source, destination) summary = { "schema_version": 1, diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py index 97413345f..43c2521bc 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare.py @@ -45,6 +45,11 @@ # codes are deliberately unresolved, even if part of the interval involved ECE. NSECE_NON_ECE_CALENDAR_CODES = frozenset({0, 50, 53, 56, 57, 60, 65}) NSECE_UNPAID_GAP_CODES = frozenset({54, 61, 62, 69}) +# HH-63, HH-483: the only substantive match-cell codes. Work status counts parents +# of any under-13 household child who attended work last week; -1 is "No parents", +# the same concept as the ASEC-side -1. HH-175: imputed income has a minimum of 0. +NSECE_REGION_CODES = frozenset({1, 2, 3, 4}) +NSECE_PARENT_WORK_STATUS_CODES = frozenset({-1, 0, 1, 2}) @dataclass(frozen=True) @@ -217,6 +222,18 @@ def derive_nsece_childcare( weight = pd.to_numeric(rows[f"HHC4_METH_WEIGHT_{child}"], errors="raise") if not np.isfinite(weight).all() or (weight <= 0).any(): raise ValueError("Existing NSECE children require positive child weights.") + # Reserve codes (negative income, unlisted region/work status) must never + # become substantive matching cells. + income = pd.to_numeric(rows.HH4_ECON_INCOME_ANNUAL, errors="raise") + if ( + not rows.HH4_REGION.isin(NSECE_REGION_CODES).all() + or not rows.HH4_PARWORK_STATUS.isin(NSECE_PARENT_WORK_STATUS_CODES).all() + or not np.isfinite(income).all() + or (income < 0).any() + ): + raise ValueError( + "NSECE region, parent work status or income holds an unlisted or reserve code." + ) part = pd.DataFrame( { "donor_id": [f"nsece2024:{case}:{child}" for case in rows.index], diff --git a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py index 471dcb43c..5f79adc5b 100644 --- a/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py +++ b/packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py @@ -29,7 +29,8 @@ def bridge_nsece_noncalendar_attendance( ) -> NSECEChildcareSource: """Use nearest regular-hour donors, retaining all ties at the cutoff. - All matching levels require the same regular-care participation status. + All matching levels require the same regular-care participation status. A + cell with fewer donors than the cutoff widens to the next matching level. Hours cannot exceed 24 per attended day or 168 per week. The parent source remains unchanged. Synthetic validation must mask whole households before this function is called; it never learns from completed bridge records. @@ -52,21 +53,29 @@ def bridge_nsece_noncalendar_attendance( & children.regular_hours_per_week.notna() ] unsupported = 0 + matching_levels: dict[str, int] = {} month, days_column, hours_column = US_CHILDCARE_ATTENDANCE_COLUMNS for index, child in target.iterrows(): - candidates = pool.iloc[:0] - for level in (NSECE_CHILDCARE_MATCH_COLUMNS, *NSECE_CHILDCARE_FALLBACK_COLUMNS): + candidates, level = pool.iloc[:0], () + for columns in ( + NSECE_CHILDCARE_MATCH_COLUMNS, + *NSECE_CHILDCARE_FALLBACK_COLUMNS, + ): mask = pool.has_regular_care.eq(child.regular_hours_per_week > 0) - for column in level: + for column in columns: mask &= pool[column].eq(child[column]) - candidates = pool.loc[ + cell = pool.loc[ mask & ( (pool.irregular_hours_per_week + child.regular_hours_per_week) <= 168 ) ] - if not candidates.empty: + # A thin cell keeps every donor however distant in regular hours; + # widen until the nearest-donor cutoff can bind. + if len(cell) > len(candidates): + candidates, level = cell, columns + if len(candidates) >= nearest_donors: break if candidates.empty: unsupported += 1 @@ -105,7 +114,9 @@ def bridge_nsece_noncalendar_attendance( children.loc[index, "irregular_hours_per_week"] = donor.irregular_hours_per_week children.loc[index, "attendance_status"] = "summary_bridge" children.loc[index, "schedule_bridge_donor"] = donor.donor_id - children.loc[index, "schedule_bridge_match"] = ",".join(level) + matched = ",".join(level) + children.loc[index, "schedule_bridge_match"] = matched + matching_levels[matched] = matching_levels.get(matched, 0) + 1 completed = children.attendance_status.eq("summary_bridge") return NSECEChildcareSource( children, @@ -116,6 +127,7 @@ def bridge_nsece_noncalendar_attendance( "seed": seed, "completed_children": int(completed.sum()), "unsupported_children": unsupported, + "matching_levels": dict(sorted(matching_levels.items())), "observed": "regular weekly hours from May/fall questionnaire", "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", diff --git a/packages/microcosm-build/tests/test_frame_serializer_registry.py b/packages/microcosm-build/tests/test_frame_serializer_registry.py index 07bb7bb1e..3de288470 100644 --- a/packages/microcosm-build/tests/test_frame_serializer_registry.py +++ b/packages/microcosm-build/tests/test_frame_serializer_registry.py @@ -361,12 +361,14 @@ def _round_trip_childcare_candidate( source = _dtype_family_table(nullable_case) before = source.copy(deep=True) path = tmp_path / "childcare-candidate.h5" - _write_childcare_candidate_person_table(path, source, {"test_receipt": "preserved"}) + receipt = {"childcare_attendance_stage": {"test_receipt": "preserved"}} + # Unrelated frame metadata must never reach the persisted receipt. + _write_childcare_candidate_person_table( + path, source, {**receipt, "unrelated_metadata": "dropped"} + ) with pd.HDFStore(path, mode="r") as store: loaded = read_frame_table(store, "person") - assert json.loads(store["_childcare_attendance_receipt"].iloc[0]) == { - "test_receipt": "preserved" - } + assert json.loads(store["_childcare_attendance_receipt"].iloc[0]) == receipt return _semantic_observation(source, before, loaded) diff --git a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py index a42ebd607..f4d0d7f76 100644 --- a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py +++ b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py @@ -4912,6 +4912,11 @@ def fake_capital_gain_details_signal_gate(frame): details={"checked": True}, ), ) + # The fake frame carries no NSECE attendance stage; the fail-fast refusal + # has its own test below. + monkeypatch.setattr( + builder, "_require_bound_childcare_attendance", lambda frame: None + ) monkeypatch.setattr( builder, @@ -12313,6 +12318,16 @@ def test_attendance_source_cli_requires_paired_inputs(options): ) +def test_build_without_attendance_source_is_refused_before_calibration(): + # No attendance columns and no receipt: what every base without the stage has. + frame = SimpleNamespace( + table=lambda entity: pd.DataFrame({"person_id": [1], "age": [4]}), + metadata={}, + ) + with pytest.raises(RuntimeError, match="--childcare-attendance-household-tsv"): + _load_builder_module()._require_bound_childcare_attendance(frame) + + def test_attendance_integrity_is_unconditional_at_final_native_write(): """Neither coverage overrides nor omitted TSV flags can skip integrity. diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index 7f5103403..f7346787d 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -751,6 +751,36 @@ def test_bridge_retains_all_equally_near_donors(): assert bridged.irregular_hours_per_week.eq(2).all() +def test_bridge_widens_a_thin_cell_instead_of_keeping_distant_donors(): + from microcosm.build.us_runtime.nsece_childcare_bridge import ( + bridge_nsece_noncalendar_attendance, + ) + + hh, cal = _raw(12) + # The target's exact cell holds one donor with 1 regular hour on 1 day; + # ten donors in another region match its 40 regular hours over 5 days. + _care(cal, row=0, hours=1) + for row in range(1, 11): + hh.loc[row, "HH4_REGION"] = 2 + for day in range(5): + _care(cal, row=row, day=day, hours=8) + hh.loc[11, "HH4_METH_QUEXVERSION"] = 2 + hh.loc[11, "HH4_MISSING_STATUS_CC_1"] = 0 + for kind in range(1, 10): + hh.loc[11, f"HHC4_NPC_HRSWEEK_TOC{kind}_1"] = 0 + hh.loc[11, "HHC4_NPC_HRSWEEK_TOC4_1"] = 40 + result = bridge_nsece_noncalendar_attendance( + derive_nsece_childcare(hh, cal), seed=915 + ) + child = result.children.iloc[11] + assert child.schedule_bridge_match == "age,parent_work_status,income_band" + assert child[DAYS] == 5 + assert child[HOURS] == 8 + assert result.source_receipt["noncalendar_bridge"]["matching_levels"] == { + "age,parent_work_status,income_band": 1 + } + + def _replace(frame, *, people=None, metadata=None): tables = {e: frame.table(e).copy() for e in frame.entities} if people is not None: @@ -919,6 +949,33 @@ def test_production_stage_refuses_unbound_existing_values(monkeypatch): ) +def test_default_outside_domain_policy_fails_early_and_names_the_flag(monkeypatch): + monkeypatch.setattr(stage, "load_nsece_childcare", lambda *args: _source()) + with pytest.raises(ValueError, match="inherit-outside-domain-baseline"): + stage.with_us_childcare_attendance_inputs( + _asec_frame(), + household_tsv="fake", + calendar_tsv="fake", + asec_source_cache=None, + seed=915, + ) + + +@pytest.mark.parametrize( + "column,value", + [ + ("HH4_REGION", -1), + ("HH4_PARWORK_STATUS", -8), + ("HH4_ECON_INCOME_ANNUAL", -9), + ], +) +def test_reserve_codes_never_become_matching_cells(column, value): + hh, cal = _raw() + hh[column] = value + with pytest.raises(ValueError, match="reserve code"): + derive_nsece_childcare(hh, cal) + + def test_final_gate_cannot_accept_unbound_nondegenerate_columns(): candidate = _replace(_candidate(), metadata={}) manifest = ReleaseInputCoverageManifest( @@ -954,7 +1011,9 @@ def test_changed_recipe_invalidates_receipt(monkeypatch): receipts, "attendance_recipe_identity", lambda: {"different": True} ) with pytest.raises(ValueError, match="recipe changed"): - assert_bound_childcare_attendance(candidate, require_stage=False) + assert_bound_childcare_attendance(candidate) + # Read-only ingress checks content only, so released files stay loadable. + assert_bound_childcare_attendance(candidate, require_stage=False) def test_public_metadata_omits_person_hash_inventory(): diff --git a/tools/build_us_fiscal_refresh_release.py b/tools/build_us_fiscal_refresh_release.py index 7dab96cbf..b7c0e37fb 100644 --- a/tools/build_us_fiscal_refresh_release.py +++ b/tools/build_us_fiscal_refresh_release.py @@ -2652,7 +2652,31 @@ def _load_frame(path: Path, *, expected_sha256: str | None = None) -> Frame: {"household": Weights(weights, WeightKind.CALIBRATED)}, ) refuse_denied_frame(frame, consumer=consumer) - return frame + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + restore_native_childcare_receipt, + ) + + return restore_native_childcare_receipt(path, frame) + + +def _require_bound_childcare_attendance(frame: Frame) -> None: + """Refuse before calibration a build that cannot export required attendance.""" + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + assert_bound_childcare_attendance, + ) + from microcosm.build.us_runtime.nsece_childcare import ( + assert_childcare_attendance_exportable, + ) + + try: + assert_childcare_attendance_exportable(frame) + assert_bound_childcare_attendance(frame) + except ValueError as error: + raise RuntimeError( + "Release gates failed: Childcare-attendance inputs failed: " + f"{error} Supply --childcare-attendance-household-tsv and " + "--childcare-attendance-calendar-tsv." + ) from error def _resolve_selection_source(args): @@ -3353,6 +3377,7 @@ def _with_aca_marketplace_source_outputs( frame.schema, {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, frame.strata, + metadata=frame.metadata, ) @@ -9346,19 +9371,6 @@ def _main(argv: Sequence[str] | None = None) -> None: time_period=PERIOD, allow_existing_without_source=True, ) - if args.childcare_attendance_household_tsv is not None: - from microcosm.build.us_runtime.childcare_attendance_stage import ( - with_us_childcare_attendance_inputs, - ) - - base_frame = with_us_childcare_attendance_inputs( - base_frame, - household_tsv=args.childcare_attendance_household_tsv, - calendar_tsv=args.childcare_attendance_calendar_tsv, - asec_source_cache=args.childcare_attendance_asec_cache, - seed=args.seed, - inherit_outside_domain_baseline=args.childcare_attendance_inherit_outside_domain_baseline, - ) childcare_gate = us_childcare_signal_gate(base_frame) if not childcare_gate.passed: if telemetry is not None: @@ -9562,6 +9574,25 @@ def _main(argv: Sequence[str] | None = None) -> None: for failure in hours_worked_gate.failures ) ) + # The attendance harmonizer reads hours_worked_last_week, so it follows the + # childcare-expense and hours producers. + if args.childcare_attendance_household_tsv is not None: + from microcosm.build.us_runtime.childcare_attendance_stage import ( + with_us_childcare_attendance_inputs, + ) + + base_frame = with_us_childcare_attendance_inputs( + base_frame, + household_tsv=args.childcare_attendance_household_tsv, + calendar_tsv=args.childcare_attendance_calendar_tsv, + asec_source_cache=args.childcare_attendance_asec_cache, + seed=args.seed, + inherit_outside_domain_baseline=args.childcare_attendance_inherit_outside_domain_baseline, + ) + else: + # Attendance is a required, non-waivable export input: without the + # source stage this run can only end red after calibration. + _require_bound_childcare_attendance(base_frame) if telemetry is not None: telemetry.stage( "snap_take_up_inputs", @@ -11365,8 +11396,14 @@ def _main(argv: Sequence[str] | None = None) -> None: ) input_coverage_gate = None if input_coverage_gate is not None: - input_coverage_failed = ( - not input_coverage_gate.passed and not args.allow_input_coverage_gaps + # Attendance integrity is not waivable: keep its failure in the batched + # report so the run retains its weight evidence instead of dying at + # the final native write below. + attendance_unbound = ( + input_coverage_gate.details.get("childcare_attendance") is None + ) + input_coverage_failed = not input_coverage_gate.passed and ( + attendance_unbound or not args.allow_input_coverage_gaps ) if input_coverage_failed: terminal_gate_failures.extend( diff --git a/tools/prepare_us_childcare_attendance.py b/tools/prepare_us_childcare_attendance.py index 2b0453ac5..6937c0acc 100644 --- a/tools/prepare_us_childcare_attendance.py +++ b/tools/prepare_us_childcare_attendance.py @@ -153,7 +153,9 @@ def main() -> None: source, seed=args.seed, match_columns=tuple(args.match_columns) ) sibling_fit = ( - fit_nsece_sibling_dependence(source.children) + fit_nsece_sibling_dependence( + source.children, match_columns=tuple(args.match_columns) + ) if args.model_sibling_dependence else {"rho": 0.0} ) From 35d40545d6a3551768df5c279a928d3877091154 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Thu, 17 Sep 2026 14:08:02 -0400 Subject: [PATCH 12/16] Rebuild the attendance candidate and evidence under the hardened recipe Rebuild from the pinned parent through the production stage, then rerun the population comparison, paired sensitivity and native-loader verification. Add a five-split masked-calendar comparison of the bridge before and after thin-cell widening. Earlier reports remain as historical evidence. Co-Authored-By: Claude Fable 5.1 --- docs/us-childcare-attendance.md | 11 +- experiments/us-childcare-attendance/README.md | 133 +- .../bridge-widening-masked-splits.json | 194 + .../bridge_widening_masked_splits.py | 152 + .../review-fixes-population.json | 1590 ++++++++ .../review-fixes-sensitivity.json | 3245 +++++++++++++++++ .../review-fixes-source-stage.json | 2013 ++++++++++ .../review-fixes-verification.json | 39 + 8 files changed, 7372 insertions(+), 5 deletions(-) create mode 100644 experiments/us-childcare-attendance/bridge-widening-masked-splits.json create mode 100644 experiments/us-childcare-attendance/bridge_widening_masked_splits.py create mode 100644 experiments/us-childcare-attendance/review-fixes-population.json create mode 100644 experiments/us-childcare-attendance/review-fixes-sensitivity.json create mode 100644 experiments/us-childcare-attendance/review-fixes-source-stage.json create mode 100644 experiments/us-childcare-attendance/review-fixes-verification.json diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index d3146a8fc..c5ba785d6 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -15,8 +15,9 @@ build inputs; CI does not download or redistribute survey records. This PR provides build integration and a local population candidate under review; it does not publish a replacement population or certify national CCDF spending. -The September 16 candidate was rebuilt from the pinned parent under -PolicyEngine-US 2.2.1 and Core 3.32.5. Both native loaders verify the new receipt; +The September 17 candidate was rebuilt from the pinned parent under +PolicyEngine-US 2.2.1 and Core 3.32.5, after the receipt, builder and bridge +changes from the second review. Both native loaders verify the new receipt; all original values and weights are preserved. The new all-state comparison reduces all-zero results from 31 jurisdictions to two (MD and NV). The [aggregate experiment](../experiments/us-childcare-attendance/README.md) records @@ -24,8 +25,10 @@ the current population, sensitivity and model diagnostics. Older 1.819.0 reports remain historical; their receipts are not reused under the new runtime. The latest diagnostic keeps each child's selected donor fixed when varying -modeled schedules. It flags IL and TN for irregular-care sensitivity, plus an OK -day sensitivity of only about $2. An interval-informed QRF experiment uses the +modeled schedules. It flags SD, TN and WV for irregular-care sensitivity, WV for +one more day, plus an OK day sensitivity of only about $2. The September 16 +flag for IL disappeared when the bridge change moved a few donor draws, so state +flags are sensitive to single draws. An interval-informed QRF experiment uses the measured bounds of incomplete training calendars, but still underpredicts weekly hours for observed children with unresolved siblings by 37.7%. All three tested completion assumptions fail that subgroup's screens; none is integrated into diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index e1cfead84..fa3dd8196 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -1,4 +1,135 @@ -# NSECE attendance: interval training and paired sensitivity — 2026-09-16 +# NSECE attendance: review hardening and rebuilt candidate — 2026-09-17 + +**PR #916 remains draft.** A second code review found that the fiscal builder +could not finish a build with attendance, and that the native receipt path could +damage or over-share the release file. Those defects are fixed, the noncalendar +bridge now widens thin matching cells, and the candidate was rebuilt from the +pinned parent under **PolicyEngine-US 2.2.1 / Core 3.32.5**. No experimental +model is adopted, no population is published, and every report retains +`production_ready: false`. + +## What changed + +- **Receipt reaches the export.** The builder's ACA source-output step rebuilt + the frame without its metadata, so the final attendance check could never + pass. It now keeps the metadata. +- **Receipt write and content.** The release file gets only a receipt key; the + person table is no longer rewritten. The key holds only the attendance context + and binding, never other frame metadata, and only those keys are restored. +- **Recipe identity.** Code hashes and runtime versions are compared when a + stage binds and when the fiscal build exports. Read-only loads check content + only, so a released file stays readable as a reference after a dependency bump. +- **Other ingress and egress.** The builder's `--base-h5` loader restores and + checks the receipt, and the L0 refit export carries it forward. +- **Fail early.** A build with neither the NSECE files nor bound attendance is + refused before calibration. An unbound-attendance failure stays in the batched + gate report, so such a run keeps its weight evidence. The `require_observed` + outside-domain policy fails before the bridge and names the flag that fixes it. +- **Source codes.** The loader rejects any region outside 1–4, any parent-work + code outside −1, 0, 1, 2, and negative income. The pinned files hold no other + values. The User's Guide (HH-483) defines −1 as "No parents" and the measure as + work attended in the week before the interview, the same concept as the ASEC + side; income (HH-175) has a minimum of 0. +- **Not changed.** The exact-k ladder wrapper cannot yet pass the NSECE flags. + Two early builder repair steps still drop frame metadata before the attendance + stage, so a receipted candidate reused as `--base-h5` is refused early instead + of being reused. + +## Bridge widening + +A matching cell with fewer than ten donors kept every donor, however distant its +regular hours, so the nearest-hours step did nothing in thin cells. Such a cell +now widens to the next matching level. Of the 3,046 bridged source children, +1,382 stay at the finest level, 1,287 drop region, 331 also drop income, and 46 +match on age alone. + +[Five whole-household masked-calendar splits](bridge-widening-masked-splits.json) +compare the two implementations child by child +([script](bridge_widening_masked_splits.py)). Regular weekly hours stay observed, +as in the bridge itself. + +| Mean over five splits | Previous | Widened | +| --- | ---: | ---: | +| Mean days error | +0.077 | +0.076 | +| Mean weekly-hours error | +0.281 | +0.258 | +| Mean absolute days error | 0.922 | 0.831 | +| Mean absolute days error, regular-care children | 1.476 | 1.215 | +| Mean absolute weekly-hours error | 3.082 | 3.064 | +| Mean absolute hours-per-day error, regular-care children | 2.872 | 2.274 | +| Share above 12 hours per day (measured 4.2%) | 7.3% | 5.7% | + +On the single split that the source-stage report uses, the overall mean +weekly-hours error rises from +0.06 to +0.79 hours, and the age-5 group mean +from 17.5 to 25.0 hours against 15.8 measured. One child with a large weight +draws a donor with 158 weekly irregular hours. Per-child days and hours-per-day +errors still fall on that split. These splits were inspected after the change +was made and are development evidence, not untouched validation. They test +reconstruction where calendars exist, not the unobserved days or irregular care +of the summer and fall instruments. + +## Rebuilt candidate + +The candidate again contains **166,321 people, 57,240 households and 31,889 +children ages 0–12**, with every under-13 attendance input resolved. Both native +loaders verify the new receipt; every original value and weight is preserved, +and attendance survives the native reload exactly. + +| Under-13 population, weighted | 2026-09-16 | 2026-09-17 | +| --- | ---: | ---: | +| Attendance share | 48.47% | 48.48% | +| Days per week | 1.959 | 1.957 | +| Hours per week | 14.08 | 14.10 | +| Annual potential modeled benefits | $5.998 billion | $6.206 billion | + +The attendance-only counterfactual still reduces all-zero results from **31 to 2 +jurisdictions (MD and NV)**, and the baseline remains $2.254 billion. Benefits +use 2026 policies on the parent's fixed ages and incomes, without aging or +uprating; they are not calibrated spending or caseload estimates, and positive +benefits do not validate attendance. Target-side matching levels are unchanged +(31,152 / 576 / 161); only the bridged donors' schedules moved. + +| Paired assumption change | Children with changed attendance | Annual potential benefits | Change from candidate | +| --- | ---: | ---: | ---: | +| No modeled irregular hours | 1,429 | $5.985 billion | −3.56% | +| One fewer modeled day | 3,815 | $6.203 billion | −0.04% | +| One more modeled day | 4,404 | $6.235 billion | +0.48% | + +All three national changes stay below the unchanged provisional 10% screen. Five +state comparisons exceed the unchanged 20% screen. Removing irregular care +changes **SD by −49.7% (−$2.9 million)**, **TN by −36.0% (−$43.3 million)** and +**WV by −31.1% (−$4.2 million)**. One more day changes **WV by +38.9% +(+$5.3 million)**, and one fewer day changes OK by −40.0% (−$2.06). IL, flagged +at −29.4% on September 16, is now −1.0%: its flag depended on which bridge donors +a few heavily weighted children drew. TN is unchanged to the dollar. State +results remain sensitive to single donor draws and to the unmeasured +irregular-care assumption. + +- [Source-stage report](review-fixes-source-stage.json) +- [Population and all-state comparison](review-fixes-population.json) +- [Native-loader verification](review-fixes-verification.json) +- [Paired noncalendar sensitivity](review-fixes-sensitivity.json) + +The four reports' code hashes match the checked-in files. The September 16 +reports below are historical: their receipts bind the previous recipe. The +calendar-selection, QRF, interval, pooling and household-size experiments reject +bridged rows and use measured calendars only, so the bridge change cannot move +their results; they were not rerun and their recorded code hashes predate this +update. + +## Code checks for this update + +Repository-wide ruff lint, changed-file formatting and the tracked CI test +inventory pass. New synthetic tests cover thin-cell widening, reserve codes, the +early outside-domain failure, the pre-calibration refusal and content-only +loading; two existing tests changed with the receipt payload and the recipe +check. **The test suite was not run locally for this update**; GitHub CI is the +first run. The production stage, native export, both native loaders and the +three validation tools ran end to end on the licensed local inputs. The fiscal +builder itself has still not been run end to end with attendance. + +## Interval training and paired sensitivity — 2026-09-16 + +This section and those below are historical. **PR #916 remains draft.** The population has now been rebuilt from the pinned parent under **PolicyEngine-US 2.2.1 / Core 3.32.5**. The latest experiment uses diff --git a/experiments/us-childcare-attendance/bridge-widening-masked-splits.json b/experiments/us-childcare-attendance/bridge-widening-masked-splits.json new file mode 100644 index 000000000..f2d0856a2 --- /dev/null +++ b/experiments/us-childcare-attendance/bridge-widening-masked-splits.json @@ -0,0 +1,194 @@ +{ + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "split_seeds": [ + 161803, + 1, + 2, + 3, + 4 + ], + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "code_sha256": { + "previous_bridge": "70a7edea8b28c6da21ff4fa9bb220766a3e6cd714926684af29bdba1a8421b0a", + "widened_bridge": "517b3c3ae02e2316b028fd15bc60a090e7962d716d46b80e3e73067f08d5ff29", + "diagnostic": "036aa608e71a30cdd6f0d4eec5db699b3ebc0eb15501ba3384d90daa6035af72" + }, + "splits": [ + { + "split_seed": 161803, + "arm": "previous", + "children": 1581, + "mean_days_error": 0.07555584784203043, + "mean_weekly_hours_error": 0.06442258174952367, + "mean_absolute_days_error": 0.8194388977061153, + "mean_absolute_days_error_regular_care": 1.416307757193369, + "mean_absolute_weekly_hours_error": 2.472029687416687, + "mean_absolute_hours_per_day_error_regular_care": 2.6749171113103776, + "share_over_12_hours_per_day": 0.06433796427135285, + "measured_share_over_12_hours_per_day": 0.04767127717601749 + }, + { + "split_seed": 161803, + "arm": "widened", + "children": 1581, + "mean_days_error": 0.08338954399965659, + "mean_weekly_hours_error": 0.7881707582301872, + "mean_absolute_days_error": 0.8178912727204896, + "mean_absolute_days_error_regular_care": 1.262449587041967, + "mean_absolute_weekly_hours_error": 3.1937517487302904, + "mean_absolute_hours_per_day_error_regular_care": 2.325289937528589, + "share_over_12_hours_per_day": 0.06917085310447134, + "measured_share_over_12_hours_per_day": 0.04767127717601749 + }, + { + "split_seed": 1, + "arm": "previous", + "children": 1498, + "mean_days_error": 0.05901730629950177, + "mean_weekly_hours_error": 0.25523504667880387, + "mean_absolute_days_error": 0.9846677194359647, + "mean_absolute_days_error_regular_care": 1.508455957977742, + "mean_absolute_weekly_hours_error": 2.637295564533044, + "mean_absolute_hours_per_day_error_regular_care": 3.2668024843376298, + "share_over_12_hours_per_day": 0.07809780461481491, + "measured_share_over_12_hours_per_day": 0.03668644349178023 + }, + { + "split_seed": 1, + "arm": "widened", + "children": 1498, + "mean_days_error": 0.07007170464299348, + "mean_weekly_hours_error": 0.4971809136961676, + "mean_absolute_days_error": 0.8516253813775049, + "mean_absolute_days_error_regular_care": 1.1339477880687852, + "mean_absolute_weekly_hours_error": 2.8984253467086747, + "mean_absolute_hours_per_day_error_regular_care": 2.161694403089898, + "share_over_12_hours_per_day": 0.06953668955621194, + "measured_share_over_12_hours_per_day": 0.03668644349178023 + }, + { + "split_seed": 2, + "arm": "previous", + "children": 1531, + "mean_days_error": 0.06827414596760045, + "mean_weekly_hours_error": 1.2760359374239916, + "mean_absolute_days_error": 0.9546795851053014, + "mean_absolute_days_error_regular_care": 1.5626003934801598, + "mean_absolute_weekly_hours_error": 3.9925135878772293, + "mean_absolute_hours_per_day_error_regular_care": 3.1923033168138097, + "share_over_12_hours_per_day": 0.09415047341304701, + "measured_share_over_12_hours_per_day": 0.040057325298559814 + }, + { + "split_seed": 2, + "arm": "widened", + "children": 1531, + "mean_days_error": 0.08053525489098147, + "mean_weekly_hours_error": 0.5300039894078206, + "mean_absolute_days_error": 0.7919407627366568, + "mean_absolute_days_error_regular_care": 1.1466730847700461, + "mean_absolute_weekly_hours_error": 3.2895767695540394, + "mean_absolute_hours_per_day_error_regular_care": 2.052681509836228, + "share_over_12_hours_per_day": 0.05670737293048663, + "measured_share_over_12_hours_per_day": 0.040057325298559814 + }, + { + "split_seed": 3, + "arm": "previous", + "children": 1537, + "mean_days_error": 0.0756790816380128, + "mean_weekly_hours_error": -0.39455595017490974, + "mean_absolute_days_error": 1.01617397353552, + "mean_absolute_days_error_regular_care": 1.6219769697494701, + "mean_absolute_weekly_hours_error": 3.740383729412565, + "mean_absolute_hours_per_day_error_regular_care": 3.112855735923538, + "share_over_12_hours_per_day": 0.08215998229723588, + "measured_share_over_12_hours_per_day": 0.05475743603089253 + }, + { + "split_seed": 3, + "arm": "widened", + "children": 1537, + "mean_days_error": 0.07374619811894458, + "mean_weekly_hours_error": -0.7280626961939485, + "mean_absolute_days_error": 0.925768148568642, + "mean_absolute_days_error_regular_care": 1.425660250964262, + "mean_absolute_weekly_hours_error": 3.3817656077824663, + "mean_absolute_hours_per_day_error_regular_care": 2.7577762584587044, + "share_over_12_hours_per_day": 0.0541435371411225, + "measured_share_over_12_hours_per_day": 0.05475743603089253 + }, + { + "split_seed": 4, + "arm": "previous", + "children": 1438, + "mean_days_error": 0.10673552047128448, + "mean_weekly_hours_error": 0.20305407843145099, + "mean_absolute_days_error": 0.8338693326761498, + "mean_absolute_days_error_regular_care": 1.2702457836361305, + "mean_absolute_weekly_hours_error": 2.5668019738013648, + "mean_absolute_hours_per_day_error_regular_care": 2.1150567518211094, + "share_over_12_hours_per_day": 0.04550264288404825, + "measured_share_over_12_hours_per_day": 0.033240843787941086 + }, + { + "split_seed": 4, + "arm": "widened", + "children": 1438, + "mean_days_error": 0.0700161012601131, + "mean_weekly_hours_error": 0.20505374309392338, + "mean_absolute_days_error": 0.7668603978885158, + "mean_absolute_days_error_regular_care": 1.1066075269633475, + "mean_absolute_weekly_hours_error": 2.554031477778747, + "mean_absolute_hours_per_day_error_regular_care": 2.0714607823879065, + "share_over_12_hours_per_day": 0.033934274927228544, + "measured_share_over_12_hours_per_day": 0.033240843787941086 + } + ], + "mean_over_splits": { + "previous": { + "mean_days_error": 0.07705238044368598, + "mean_weekly_hours_error": 0.280838338821772, + "mean_absolute_days_error": 0.9217659016918102, + "mean_absolute_days_error_regular_care": 1.4759173724073742, + "mean_absolute_weekly_hours_error": 3.081804908608178, + "mean_absolute_hours_per_day_error_regular_care": 2.872387080041293, + "share_over_12_hours_per_day": 0.07284977349609978, + "measured_share_over_12_hours_per_day": 0.04248266515703823 + }, + "widened": { + "mean_days_error": 0.07555176058253785, + "mean_weekly_hours_error": 0.2584693416468301, + "mean_absolute_days_error": 0.8308171926583618, + "mean_absolute_days_error_regular_care": 1.2150676475616815, + "mean_absolute_weekly_hours_error": 3.0635101901108435, + "mean_absolute_hours_per_day_error_regular_care": 2.273780578260266, + "share_over_12_hours_per_day": 0.05669854553190419, + "measured_share_over_12_hours_per_day": 0.04248266515703823 + } + }, + "production_ready": false, + "interpretation": "Development evidence on calendars that exist; not a test of the unobserved days or irregular care of the noncalendar instruments." +} diff --git a/experiments/us-childcare-attendance/bridge_widening_masked_splits.py b/experiments/us-childcare-attendance/bridge_widening_masked_splits.py new file mode 100644 index 000000000..24c5efd11 --- /dev/null +++ b/experiments/us-childcare-attendance/bridge_widening_masked_splits.py @@ -0,0 +1,152 @@ +"""Compare the noncalendar bridge before and after thin-cell widening. + +Development diagnostic only. It repeats the whole-household masked-calendar +design of ``assess_noncalendar_bridge`` over several split seeds and reports +per-child errors, which the group means in the source-stage report cannot show. +The previous implementation is loaded from a file, e.g.:: + + git show e1b5d6c7:packages/microcosm-build/src/microcosm/build/us_runtime/nsece_childcare_bridge.py > /local/previous_bridge.py + uv run python experiments/us-childcare-attendance/bridge_widening_masked_splits.py \ + --household-tsv /local/39466-0005-Data.tsv \ + --calendar-tsv /local/39466-0004-Data.tsv \ + --previous-bridge /local/previous_bridge.py \ + --report /local/bridge-widening-masked-splits.json + +Only aggregates are written; no survey records or donor identities. +""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import json +from pathlib import Path + +import numpy as np + +from microcosm.build.us_runtime import nsece_childcare_bridge as current_bridge +from microcosm.build.us_runtime.nsece_childcare import ( + NSECEChildcareSource, + load_nsece_childcare, +) + +SPLIT_SEEDS = (161803, 1, 2, 3, 4) +MASKED_COLUMNS = [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day", + "ece_hours_per_week", + "irregular_hours_per_week", +] + + +def _sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _load(path: Path): + spec = importlib.util.spec_from_file_location("previous_bridge", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _split(source: NSECEChildcareSource, seed: int): + children = source.children.copy() + complete = children.attendance_status.eq("complete") + holdout = complete & children.source_household_id.map( + lambda x: ( + int.from_bytes(hashlib.sha256(f"{seed}:{x}".encode()).digest()[:8], "big") + % 5 + == 0 + ) + ) + truth = children.loc[holdout].copy() + children.loc[~complete, "regular_hours_per_week"] = np.nan + children.loc[holdout, "attendance_status"] = "missing_calendar" + children.loc[holdout, "questionnaire_version"] = 2 + children.loc[holdout, MASKED_COLUMNS] = np.nan + masked = NSECEChildcareSource(children, source.weights, source.source_receipt) + return masked, truth, holdout + + +def _errors(predicted, truth) -> dict: + weight = truth.child_weight + regular = truth.regular_hours_per_week > 0 + + def average(values, mask=None): + mask = slice(None) if mask is None else mask + return float(np.average(values[mask], weights=weight[mask])) + + days = predicted.childcare_days_per_week - truth.childcare_days_per_week + hours = predicted.ece_hours_per_week - truth.ece_hours_per_week + daily = predicted.childcare_hours_per_day - truth.childcare_hours_per_day + return { + "children": len(truth), + "mean_days_error": average(days), + "mean_weekly_hours_error": average(hours), + "mean_absolute_days_error": average(days.abs()), + "mean_absolute_days_error_regular_care": average(days.abs(), regular), + "mean_absolute_weekly_hours_error": average(hours.abs()), + "mean_absolute_hours_per_day_error_regular_care": average(daily.abs(), regular), + "share_over_12_hours_per_day": average( + (predicted.childcare_hours_per_day > 12).astype(float) + ), + "measured_share_over_12_hours_per_day": average( + (truth.childcare_hours_per_day > 12).astype(float) + ), + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--household-tsv", type=Path, required=True) + parser.add_argument("--calendar-tsv", type=Path, required=True) + parser.add_argument("--previous-bridge", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if args.report.exists(): + parser.error("The report path must be new") + arms = {"previous": _load(args.previous_bridge), "widened": current_bridge} + source = load_nsece_childcare(args.household_tsv, args.calendar_tsv) + splits = [] + for seed in SPLIT_SEEDS: + masked, truth, holdout = _split(source, seed) + for arm, module in arms.items(): + bridged = module.bridge_nsece_noncalendar_attendance(masked, seed=seed) + predicted = bridged.children.loc[holdout] + if not predicted.attendance_status.eq("summary_bridge").all(): + raise ValueError("Masked-calendar bridge has unsupported records") + splits.append({"split_seed": seed, "arm": arm, **_errors(predicted, truth)}) + metrics = [k for k in splits[0] if k not in ("split_seed", "arm", "children")] + report = { + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "split_seeds": list(SPLIT_SEEDS), + "source": source.source_receipt, + "code_sha256": { + "previous_bridge": _sha256(args.previous_bridge), + "widened_bridge": _sha256(Path(current_bridge.__file__)), + "diagnostic": _sha256(Path(__file__)), + }, + "splits": splits, + "mean_over_splits": { + arm: { + metric: float(np.mean([s[metric] for s in splits if s["arm"] == arm])) + for metric in metrics + } + for arm in arms + }, + "production_ready": False, + "interpretation": ( + "Development evidence on calendars that exist; not a test of the " + "unobserved days or irregular care of the noncalendar instruments." + ), + } + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n") + print(json.dumps(report["mean_over_splits"], indent=2)) + + +if __name__ == "__main__": + main() diff --git a/experiments/us-childcare-attendance/review-fixes-population.json b/experiments/us-childcare-attendance/review-fixes-population.json new file mode 100644 index 000000000..d700ff6c9 --- /dev/null +++ b/experiments/us-childcare-attendance/review-fixes-population.json @@ -0,0 +1,1590 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "candidate_checkpoint_sha256": "5cb9aa418768b5a82ee351e8b737f58c8989212f1ffbee36d605c22887e989a5", + "validation_code_sha256": "ee3ca65dcd2ef4492924edce54b6bdd03b8b4ed882c65684fc57a1622c341dfe", + "engine_version": "2.2.1", + "policy_year": 2026, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "people": 166321, + "households": 57240, + "under13_children": 31889, + "outside_source_domain_people": 134432, + "outside_source_domain_age13_17_with_disability": 557, + "outside_domain_baseline_receipt": { + "engine_version": "2.2.1", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "unresolved_people_retaining_baseline": 0, + "unresolved_age13_17_with_disability": 0, + "weighted_under13_attendance_share": 0.48476583170284354, + "weighted_under13_days_per_week": 1.9567197164255687, + "weighted_under13_hours_per_week": 14.09681286757626, + "candidate_receipt": { + "nsece_childcare_attendance": { + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "calendar_bounds": { + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "missing_calendar": "uninformative 0--168 hours and 0--7 days", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568" + }, + "candidate_only": true, + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "noncalendar_bridge": { + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", + "completed_children": 3046, + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "matching_levels": { + "age": 46, + "age,parent_work_status": 331, + "age,parent_work_status,income_band": 1287, + "age,region,parent_work_status,income_band": 1382 + }, + "observed": "regular weekly hours from May/fall questionnaire", + "seed": 915, + "unsupported_children": 0 + }, + "seed": 915, + "sibling_dependence": 0.7413149380294699, + "source_year": 2024, + "study": "ICPSR39466.v1" + }, + "childcare_predictor_harmonization": { + "geography": "Census region from target state_fips", + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars", + "income_source_receipts": [ + { + "income_year": 2022, + "matched_people": 54464, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "source_rows": 146133 + }, + { + "income_year": 2023, + "matched_people": 54654, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "source_rows": 144265 + }, + { + "income_year": 2024, + "matched_people": 57203, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "source_rows": 142125 + } + ], + "parent_universe": "parents of any child age 0 through 12 in household", + "source": "ASEC resident parent line pointers", + "work_measure": "hours_worked_last_week > 0" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "childcare_attendance_stage": { + "modeled_age_domain": [ + 0, + 12 + ], + "operations": [ + { + "age_max": 12, + "age_min": 0, + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance" + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "nearest_donors": 10, + "operation": "regular_hours_schedule_bridge" + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "outside_domain_policy": "inherit_engine_baseline", + "seed": 915, + "sibling_dependence": { + "estimation": "youngest pair in fully observed households; household design weights", + "households": 1941, + "independent_both_in_care": 0.2585427028841666, + "observed_both_in_care": 0.32858869865910467, + "rho": 0.7413149380294699, + "shared_both_in_care": 0.3530315526045447, + "unconstrained_rho": 0.7413149380294699 + }, + "stage": "nsece_childcare_attendance" + }, + "childcare_attendance_binding": { + "schema_version": 1, + "binding_sha256": "5a67c4cdbefa261a6ba7be056ef37afbf6a20755c649432c10020026a7f1e9a2", + "execution_sha256": "a9c0c33d4eac2a876e7cee52c65b8e2ad3d2200bed62d91bc0465c05f590cc27", + "source_people": 166321, + "retained_people": 166321, + "execution": { + "context": { + "childcare_attendance_stage": { + "modeled_age_domain": [ + 0, + 12 + ], + "operations": [ + { + "age_max": 12, + "age_min": 0, + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance" + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "nearest_donors": 10, + "operation": "regular_hours_schedule_bridge" + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "outside_domain_policy": "inherit_engine_baseline", + "seed": 915, + "sibling_dependence": { + "estimation": "youngest pair in fully observed households; household design weights", + "households": 1941, + "independent_both_in_care": 0.2585427028841666, + "observed_both_in_care": 0.32858869865910467, + "rho": 0.7413149380294699, + "shared_both_in_care": 0.3530315526045447, + "unconstrained_rho": 0.7413149380294699 + }, + "stage": "nsece_childcare_attendance" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + } + }, + "childcare_predictor_harmonization": { + "geography": "Census region from target state_fips", + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars", + "income_source_receipts": [ + { + "income_year": 2022, + "matched_people": 54464, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "source_rows": 146133 + }, + { + "income_year": 2023, + "matched_people": 54654, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "source_rows": 144265 + }, + { + "income_year": 2024, + "matched_people": 57203, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "source_rows": 142125 + } + ], + "parent_universe": "parents of any child age 0 through 12 in household", + "source": "ASEC resident parent line pointers", + "work_measure": "hours_worked_last_week > 0" + }, + "nsece_childcare_attendance": { + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "calendar_bounds": { + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "missing_calendar": "uninformative 0--168 hours and 0--7 days", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568" + }, + "candidate_only": true, + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "noncalendar_bridge": { + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments", + "completed_children": 3046, + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "matching_levels": { + "age": 46, + "age,parent_work_status": 331, + "age,parent_work_status,income_band": 1287, + "age,region,parent_work_status,income_band": 1382 + }, + "observed": "regular weekly hours from May/fall questionnaire", + "seed": 915, + "unsupported_children": 0 + }, + "seed": 915, + "sibling_dependence": 0.7413149380294699, + "source_year": 2024, + "study": "ICPSR39466.v1" + } + }, + "recipe": { + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_attendance_receipt.py": "d93ecfe1725bef2c0b87a137eaab88856fa9a76e601227089ceb94daa4335865", + "childcare_attendance_stage.py": "c5243b7f23865c352acee8f5946bb8c7b578645c7b5e56c24632fa94dd9dbecb", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "e11eef389f47433a2d407aaf7c01214dbefccb702b3fefe8fa6502c7e947ab4d", + "nsece_childcare_bridge.py": "517b3c3ae02e2316b028fd15bc60a090e7962d716d46b80e3e73067f08d5ff29", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09" + }, + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + } + } + } + } + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 21138074.239445645 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 31, + "weighted_positive_units": 201632.45677746536, + "annual_modeled_benefits": 935799297.9584653 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 653474165.4604676 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 60231.19228101184, + "annual_modeled_benefits": 320519265.7821029 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.948581845 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709143.174893744 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 168.32094133054224 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 0.07485817919950932, + "annual_modeled_benefits": 214.60111108028417 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110018.22040879 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647115.49393122 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386147637.05533403 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 476.4295530626239 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 104095590.72541192 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809179.365034073 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178659.05015105 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 93309.70299506374, + "annual_modeled_benefits": 199602579.27443355 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 33402.23592408285, + "annual_modeled_benefits": 686206383.39497 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 254330200.2860741 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 5322.572984872386, + "annual_modeled_benefits": 13548522.496824618 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + } + } + ], + "production_ready": false, + "interpretation": "attendance-only counterfactual; provider, activity, expenses and take-up inputs remain as in parent", + "matching_levels": { + "age,region,parent_work_status,income_band": 31152, + "age,parent_work_status,income_band": 576, + "age,parent_work_status": 161 + }, + "population_attendance_by_group": [ + { + "grouping": "age", + "group": "0.0", + "children": 1689, + "weighted_children": 2750700.861434371, + "attendance_share": 0.3908649027002664, + "days_per_week": 1.4733482895551573, + "hours_per_week": 12.433364620574558 + }, + { + "grouping": "age", + "group": "1.0", + "children": 2157, + "weighted_children": 3858818.186826853, + "attendance_share": 0.5857212350699339, + "days_per_week": 2.4682816215895658, + "hours_per_week": 20.936959602461613 + }, + { + "grouping": "age", + "group": "2.0", + "children": 2364, + "weighted_children": 3754041.8444214575, + "attendance_share": 0.63217333018214, + "days_per_week": 2.625776145467515, + "hours_per_week": 21.417061319044002 + }, + { + "grouping": "age", + "group": "3.0", + "children": 2385, + "weighted_children": 3880377.4871301977, + "attendance_share": 0.6496350615721032, + "days_per_week": 2.8214495362637413, + "hours_per_week": 22.433119986053377 + }, + { + "grouping": "age", + "group": "4.0", + "children": 2482, + "weighted_children": 4358831.600945832, + "attendance_share": 0.7378382488231238, + "days_per_week": 3.4554539884839417, + "hours_per_week": 26.564435597480422 + }, + { + "grouping": "age", + "group": "5.0", + "children": 2391, + "weighted_children": 4095170.7360430276, + "attendance_share": 0.5629639983082603, + "days_per_week": 2.558431264266053, + "hours_per_week": 18.571862465758638 + }, + { + "grouping": "age", + "group": "6.0", + "children": 2562, + "weighted_children": 4029009.778184548, + "attendance_share": 0.44994930361979923, + "days_per_week": 1.6816438584425009, + "hours_per_week": 10.601418302478313 + }, + { + "grouping": "age", + "group": "7.0", + "children": 2612, + "weighted_children": 3768786.8623211696, + "attendance_share": 0.34301503193166, + "days_per_week": 1.4050708577667275, + "hours_per_week": 9.321322146324611 + }, + { + "grouping": "age", + "group": "8.0", + "children": 2689, + "weighted_children": 4419397.563846819, + "attendance_share": 0.4289583167472629, + "days_per_week": 1.572046628602081, + "hours_per_week": 9.528014323588392 + }, + { + "grouping": "age", + "group": "9.0", + "children": 2552, + "weighted_children": 3888389.065045069, + "attendance_share": 0.38602014666142587, + "days_per_week": 1.2822278907340288, + "hours_per_week": 8.230580792584078 + }, + { + "grouping": "age", + "group": "10.0", + "children": 2662, + "weighted_children": 4369978.335878741, + "attendance_share": 0.4062473204874741, + "days_per_week": 1.5012465135600794, + "hours_per_week": 7.890631718854404 + }, + { + "grouping": "age", + "group": "11.0", + "children": 2795, + "weighted_children": 4689133.259098214, + "attendance_share": 0.40248285339977075, + "days_per_week": 1.4232741547650194, + "hours_per_week": 8.373396107105151 + }, + { + "grouping": "age", + "group": "12.0", + "children": 2549, + "weighted_children": 4093051.641607744, + "attendance_share": 0.31040280481013055, + "days_per_week": 1.1000106686299516, + "hours_per_week": 7.921206635626762 + }, + { + "grouping": "region", + "group": "1", + "children": 5553, + "weighted_children": 7976838.3562431075, + "attendance_share": 0.5122082980828326, + "days_per_week": 2.137540339663031, + "hours_per_week": 14.921393569988492 + }, + { + "grouping": "region", + "group": "2", + "children": 7790, + "weighted_children": 10772583.599183697, + "attendance_share": 0.5121729637562082, + "days_per_week": 1.9819630179827, + "hours_per_week": 14.461387736332831 + }, + { + "grouping": "region", + "group": "3", + "children": 10750, + "weighted_children": 21007309.669184513, + "attendance_share": 0.44090280170545504, + "days_per_week": 1.803568625325827, + "hours_per_week": 13.322474186390497 + }, + { + "grouping": "region", + "group": "4", + "children": 7796, + "weighted_children": 12198955.598172724, + "attendance_share": 0.5181534861817509, + "days_per_week": 2.079925340499398, + "hours_per_week": 14.569132992972806 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "children": 684, + "weighted_children": 1044954.9507498589, + "attendance_share": 0.5521504831293419, + "days_per_week": 2.3270185315729743, + "hours_per_week": 25.40529368071806 + }, + { + "grouping": "parent_work_status", + "group": "0", + "children": 2876, + "weighted_children": 5039737.2909418885, + "attendance_share": 0.3074240397539245, + "days_per_week": 1.2719798150247412, + "hours_per_week": 11.03519233829625 + }, + { + "grouping": "parent_work_status", + "group": "1", + "children": 9966, + "weighted_children": 15397404.080512933, + "attendance_share": 0.30361930919230407, + "days_per_week": 0.9707253415100399, + "hours_per_week": 6.653502499604582 + }, + { + "grouping": "parent_work_status", + "group": "2", + "children": 18363, + "weighted_children": 30473590.900579363, + "attendance_share": 0.6033120290865125, + "days_per_week": 2.5554583917183713, + "hours_per_week": 17.97625615205827 + }, + { + "grouping": "income_band", + "group": "0", + "children": 94, + "weighted_children": 81378.29635578688, + "attendance_share": 0.3855688612887243, + "days_per_week": 1.1732033382726887, + "hours_per_week": 6.896472838209584 + }, + { + "grouping": "income_band", + "group": "1", + "children": 3165, + "weighted_children": 6927519.022630059, + "attendance_share": 0.4099489581643712, + "days_per_week": 1.7141304062628688, + "hours_per_week": 12.474528478758927 + }, + { + "grouping": "income_band", + "group": "2", + "children": 6097, + "weighted_children": 10396829.673697237, + "attendance_share": 0.4650799637140388, + "days_per_week": 1.9345932911088046, + "hours_per_week": 14.621868846831072 + }, + { + "grouping": "income_band", + "group": "3", + "children": 9010, + "weighted_children": 14071962.152827512, + "attendance_share": 0.4488404939306434, + "days_per_week": 1.7742390271484756, + "hours_per_week": 13.367286745016436 + }, + { + "grouping": "income_band", + "group": "4", + "children": 7428, + "weighted_children": 13192218.644710096, + "attendance_share": 0.5109061436767217, + "days_per_week": 1.9814314491639318, + "hours_per_week": 14.248294331748822 + }, + { + "grouping": "income_band", + "group": "5", + "children": 6095, + "weighted_children": 7285779.432563353, + "attendance_share": 0.6071590090255058, + "days_per_week": 2.5354095841254956, + "hours_per_week": 16.105235893357964 + } + ], + "summary": { + "states_evaluated": 51, + "all_zero_states": { + "baseline": [ + "AL", + "AZ", + "AR", + "CA", + "DC", + "FL", + "HI", + "ID", + "IL", + "IA", + "KS", + "KY", + "LA", + "MD", + "MA", + "MN", + "MS", + "MO", + "NV", + "NY", + "OH", + "OK", + "PA", + "SC", + "SD", + "TN", + "TX", + "VA", + "WV", + "WI", + "WY" + ], + "candidate": [ + "MD", + "NV" + ] + }, + "annual_potential_modeled_benefits": { + "baseline": 2253603947.6976757, + "candidate": 6205696565.309278 + } + } +} diff --git a/experiments/us-childcare-attendance/review-fixes-sensitivity.json b/experiments/us-childcare-attendance/review-fixes-sensitivity.json new file mode 100644 index 000000000..a3a268ddd --- /dev/null +++ b/experiments/us-childcare-attendance/review-fixes-sensitivity.json @@ -0,0 +1,3245 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "e11eef389f47433a2d407aaf7c01214dbefccb702b3fefe8fa6502c7e947ab4d", + "nsece_childcare_bridge.py": "517b3c3ae02e2316b028fd15bc60a090e7962d716d46b80e3e73067f08d5ff29", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "c5243b7f23865c352acee8f5946bb8c7b578645c7b5e56c24632fa94dd9dbecb", + "childcare_attendance_receipt.py": "d93ecfe1725bef2c0b87a137eaab88856fa9a76e601227089ceb94daa4335865" + } + }, + "seed": 915, + "candidate_checkpoint_sha256": "5cb9aa418768b5a82ee351e8b737f58c8989212f1ffbee36d605c22887e989a5", + "assignment_checks": { + "candidate": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9642, + "changed_people": 0, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + }, + "no_modeled_irregular_hours": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9642, + "changed_people": 1429, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + }, + "one_fewer_day": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9642, + "changed_people": 3815, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + }, + "one_more_day": { + "donor_assigned_people": 31889, + "bridge_assigned_people": 9642, + "changed_people": 4404, + "measured_calendar_assignments_changed": 0, + "donor_assignment": "held fixed and validated against candidate values" + } + }, + "coupling": "same donor identities across scenarios; no care-rank reassignment", + "engine_version": "2.2.1", + "policy_year": 2026, + "code_sha256": { + "validate_us_childcare_sensitivity.py": "ce58f0179a9b2732d2adf2335ec4444dd8a1f861b248923e9bed561c7282f983", + "childcare_sensitivity.py": "17a684c51ed48f164006d485aa2177d8c1f03609290e96eae377c625bb6d93cf" + }, + "population": "fixed BuildP source ages/incomes; no aging or uprating", + "assumptions": { + "candidate": {}, + "no_modeled_irregular_hours": { + "include_modeled_irregular_hours": false, + "modeled_day_shift": 0, + "measured_regular_hours_preserved": true + }, + "one_fewer_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": -1, + "measured_regular_hours_preserved": true + }, + "one_more_day": { + "include_modeled_irregular_hours": true, + "modeled_day_shift": 1, + "measured_regular_hours_preserved": true + } + }, + "changed_children": { + "candidate": 0, + "no_modeled_irregular_hours": 1429, + "one_fewer_day": 3815, + "one_more_day": 4404 + }, + "screens": { + "national_relative_change": 0.1, + "state_relative_change": 0.2 + }, + "states": [ + { + "state": "AL", + "sample_households": 1061, + "baseline": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "no_modeled_irregular_hours": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_fewer_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + }, + "one_more_day": { + "variable": "al_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 13061.764184842585, + "annual_modeled_benefits": 32688921.398814265 + } + }, + { + "state": "AK", + "sample_households": 826, + "baseline": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "candidate": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "no_modeled_irregular_hours": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_fewer_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + }, + "one_more_day": { + "variable": "ak_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 1390.1623900386767, + "annual_modeled_benefits": 6435797.486872292 + } + }, + { + "state": "AZ", + "sample_households": 1164, + "baseline": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 21138074.239445645 + }, + "no_modeled_irregular_hours": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18733.29978438327, + "annual_modeled_benefits": 23160681.10555565 + }, + "one_fewer_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 21137750.54961618 + }, + "one_more_day": { + "variable": "az_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 18733.37486637244, + "annual_modeled_benefits": 20238702.645136394 + } + }, + { + "state": "AR", + "sample_households": 952, + "baseline": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "no_modeled_irregular_hours": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "one_fewer_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + }, + "one_more_day": { + "variable": "ar_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 17289.894029146235, + "annual_modeled_benefits": 50793417.58586023 + } + }, + { + "state": "CA", + "sample_households": 2659, + "baseline": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 31, + "weighted_positive_units": 201632.45677746536, + "annual_modeled_benefits": 935799297.9584653 + }, + "no_modeled_irregular_hours": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 29, + "weighted_positive_units": 191015.65929813386, + "annual_modeled_benefits": 812786996.789741 + }, + "one_fewer_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 31, + "weighted_positive_units": 201632.45677746536, + "annual_modeled_benefits": 935799297.9584653 + }, + "one_more_day": { + "variable": "ca_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 31, + "weighted_positive_units": 201632.45677746536, + "annual_modeled_benefits": 935799297.9584653 + } + }, + { + "state": "CO", + "sample_households": 1164, + "baseline": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 19, + "weighted_positive_units": 30914.695305580855, + "annual_modeled_benefits": 252602567.65021172 + }, + "candidate": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "no_modeled_irregular_hours": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "one_fewer_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + }, + "one_more_day": { + "variable": "co_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 18502.53110225743, + "annual_modeled_benefits": 236445530.32343873 + } + }, + { + "state": "CT", + "sample_households": 908, + "baseline": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 405879416.70427954 + }, + "candidate": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 653474165.4604676 + }, + "no_modeled_irregular_hours": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 653474133.8050566 + }, + "one_fewer_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 653474165.4604676 + }, + "one_more_day": { + "variable": "ct_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 23, + "weighted_positive_units": 79449.487583021, + "annual_modeled_benefits": 653474165.4604676 + } + }, + { + "state": "DE", + "sample_households": 1068, + "baseline": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "candidate": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "no_modeled_irregular_hours": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_fewer_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + }, + "one_more_day": { + "variable": "de_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 13026.181364486925, + "annual_modeled_benefits": 42150126.00852959 + } + }, + { + "state": "DC", + "sample_households": 551, + "baseline": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "no_modeled_irregular_hours": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.31200898 + }, + "one_fewer_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.300562065 + }, + "one_more_day": { + "variable": "dc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 3288.0395223266023, + "annual_modeled_benefits": 23916792.180974036 + } + }, + { + "state": "FL", + "sample_households": 1642, + "baseline": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 60231.19228101184, + "annual_modeled_benefits": 320519265.7821029 + }, + "no_modeled_irregular_hours": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 49750.16103101184, + "annual_modeled_benefits": 304168857.0321029 + }, + "one_fewer_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 60231.19228101184, + "annual_modeled_benefits": 318098303.34044707 + }, + "one_more_day": { + "variable": "fl_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 60231.19228101184, + "annual_modeled_benefits": 337107334.9374325 + } + }, + { + "state": "GA", + "sample_households": 1215, + "baseline": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "candidate": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "no_modeled_irregular_hours": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_fewer_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + }, + "one_more_day": { + "variable": "ga_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 13, + "weighted_positive_units": 28445.463818153017, + "annual_modeled_benefits": 130565867.0594202 + } + }, + { + "state": "HI", + "sample_households": 838, + "baseline": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "no_modeled_irregular_hours": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "one_fewer_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + }, + "one_more_day": { + "variable": "hi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0001588361046742648, + "annual_modeled_benefits": 0.6336225438054939 + } + }, + { + "state": "ID", + "sample_households": 746, + "baseline": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "no_modeled_irregular_hours": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_fewer_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + }, + "one_more_day": { + "variable": "id_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 2, + "weighted_positive_units": 2335.738774359226, + "annual_modeled_benefits": 10651259.924815893 + } + }, + { + "state": "IL", + "sample_households": 1668, + "baseline": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + }, + "no_modeled_irregular_hours": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 194890109.83533916 + }, + "one_fewer_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786118.53858995 + }, + "one_more_day": { + "variable": "il_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 62767.78443156066, + "annual_modeled_benefits": 196786246.13666055 + } + }, + { + "state": "IN", + "sample_households": 1141, + "baseline": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "candidate": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "no_modeled_irregular_hours": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_fewer_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + }, + "one_more_day": { + "variable": "in_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 20, + "weighted_positive_units": 9694.763750434155, + "annual_modeled_benefits": 25856735.988747887 + } + }, + { + "state": "IA", + "sample_households": 973, + "baseline": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.948581845 + }, + "no_modeled_irregular_hours": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.948581845 + }, + "one_fewer_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.948581845 + }, + "one_more_day": { + "variable": "ia_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 13277.072875641927, + "annual_modeled_benefits": 55836595.948581845 + } + }, + { + "state": "KS", + "sample_households": 921, + "baseline": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709143.174893744 + }, + "no_modeled_irregular_hours": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709143.174893744 + }, + "one_fewer_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709143.174893744 + }, + "one_more_day": { + "variable": "ks_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 21785.09440057905, + "annual_modeled_benefits": 58709143.174893744 + } + }, + { + "state": "KY", + "sample_households": 776, + "baseline": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 168.32094133054224 + }, + "no_modeled_irregular_hours": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 168.32094133054224 + }, + "one_fewer_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 173.82699462903886 + }, + "one_more_day": { + "variable": "ky_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.02885724022053182, + "annual_modeled_benefits": 163.91609819387403 + } + }, + { + "state": "LA", + "sample_households": 1000, + "baseline": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + }, + "no_modeled_irregular_hours": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 69024044.59310351 + }, + "one_fewer_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + }, + "one_more_day": { + "variable": "la_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 37176.93537535961, + "annual_modeled_benefits": 72842360.51107226 + } + }, + { + "state": "ME", + "sample_households": 785, + "baseline": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683663.687080294 + }, + "candidate": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "no_modeled_irregular_hours": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_fewer_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + }, + "one_more_day": { + "variable": "me_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 17, + "weighted_positive_units": 11874.206514407007, + "annual_modeled_benefits": 46683670.942570195 + } + }, + { + "state": "MD", + "sample_households": 1091, + "baseline": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "md_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "MA", + "sample_households": 1522, + "baseline": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 0.07485817919950932, + "annual_modeled_benefits": 214.60111108028417 + }, + "no_modeled_irregular_hours": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 0.07485817919950932, + "annual_modeled_benefits": 214.60111108028417 + }, + "one_fewer_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 0.07485817919950932, + "annual_modeled_benefits": 214.60111108028417 + }, + "one_more_day": { + "variable": "ma_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 0.07485817919950932, + "annual_modeled_benefits": 214.60111108028417 + } + }, + { + "state": "MI", + "sample_households": 1478, + "baseline": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "candidate": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "no_modeled_irregular_hours": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_fewer_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + }, + "one_more_day": { + "variable": "mi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 42453.16293835803, + "annual_modeled_benefits": 167344216.73651382 + } + }, + { + "state": "MN", + "sample_households": 1166, + "baseline": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + }, + "no_modeled_irregular_hours": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + }, + "one_fewer_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + }, + "one_more_day": { + "variable": "mn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 21289.401307827327, + "annual_modeled_benefits": 274850426.2413397 + } + }, + { + "state": "MS", + "sample_households": 882, + "baseline": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + }, + "no_modeled_irregular_hours": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 7256.951193145127, + "annual_modeled_benefits": 30449886.57411563 + }, + "one_fewer_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + }, + "one_more_day": { + "variable": "ms_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 7256.952556610806, + "annual_modeled_benefits": 30449890.664512668 + } + }, + { + "state": "MO", + "sample_households": 1013, + "baseline": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110018.22040879 + }, + "no_modeled_irregular_hours": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110018.22040879 + }, + "one_fewer_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110019.68683209 + }, + "one_more_day": { + "variable": "mo_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 9981.891486888984, + "annual_modeled_benefits": 44110017.04727027 + } + }, + { + "state": "MT", + "sample_households": 744, + "baseline": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "candidate": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "no_modeled_irregular_hours": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_fewer_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + }, + "one_more_day": { + "variable": "mt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 3, + "weighted_positive_units": 4.609087781107519, + "annual_modeled_benefits": 343.68633474948 + } + }, + { + "state": "NE", + "sample_households": 884, + "baseline": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 754.4826039844379 + }, + "candidate": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "no_modeled_irregular_hours": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "one_fewer_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + }, + "one_more_day": { + "variable": "ne_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 1.0507851635920815, + "annual_modeled_benefits": 752.3048707440652 + } + }, + { + "state": "NV", + "sample_households": 952, + "baseline": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "no_modeled_irregular_hours": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_fewer_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "one_more_day": { + "variable": "nv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + } + }, + { + "state": "NH", + "sample_households": 840, + "baseline": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "candidate": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "no_modeled_irregular_hours": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_fewer_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + }, + "one_more_day": { + "variable": "nh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 6328.660297448034, + "annual_modeled_benefits": 16452735.29621234 + } + }, + { + "state": "NJ", + "sample_households": 1651, + "baseline": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 54783391.64412939 + }, + "candidate": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "no_modeled_irregular_hours": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_fewer_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + }, + "one_more_day": { + "variable": "nj_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 47645.06307415466, + "annual_modeled_benefits": 39399036.82068616 + } + }, + { + "state": "NM", + "sample_households": 902, + "baseline": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 40276267.46262546 + }, + "candidate": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647115.49393122 + }, + "no_modeled_irregular_hours": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647115.49393122 + }, + "one_fewer_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647115.49393122 + }, + "one_more_day": { + "variable": "nm_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 32, + "weighted_positive_units": 14416.283866321406, + "annual_modeled_benefits": 59647115.49393122 + } + }, + { + "state": "NY", + "sample_households": 2166, + "baseline": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386147637.05533403 + }, + "no_modeled_irregular_hours": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386147637.05533403 + }, + "one_fewer_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386146572.1787399 + }, + "one_more_day": { + "variable": "ny_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 28, + "weighted_positive_units": 84809.87077922659, + "annual_modeled_benefits": 386148701.9332047 + } + }, + { + "state": "NC", + "sample_households": 1337, + "baseline": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "candidate": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "no_modeled_irregular_hours": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_fewer_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + }, + "one_more_day": { + "variable": "nc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 28725.60765656957, + "annual_modeled_benefits": 161822256.86665964 + } + }, + { + "state": "ND", + "sample_households": 682, + "baseline": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 409.12895149587507 + }, + "candidate": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 476.4295530626239 + }, + "no_modeled_irregular_hours": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 471.04686684011335 + }, + "one_fewer_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 476.4295530626239 + }, + "one_more_day": { + "variable": "nd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.1099370562878903, + "annual_modeled_benefits": 476.4295530626239 + } + }, + { + "state": "OH", + "sample_households": 1531, + "baseline": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 104095590.72541192 + }, + "no_modeled_irregular_hours": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 104095590.72541192 + }, + "one_fewer_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 104095590.72541192 + }, + "one_more_day": { + "variable": "oh_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 10, + "weighted_positive_units": 57197.27386227145, + "annual_modeled_benefits": 104095590.72541192 + } + }, + { + "state": "OK", + "sample_households": 918, + "baseline": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "no_modeled_irregular_hours": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + }, + "one_fewer_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 3.0897752604315514 + }, + "one_more_day": { + "variable": "ok_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 1, + "weighted_positive_units": 0.0031096774619072676, + "annual_modeled_benefits": 5.149625876918435 + } + }, + { + "state": "OR", + "sample_households": 1152, + "baseline": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "candidate": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "no_modeled_irregular_hours": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_fewer_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + }, + "one_more_day": { + "variable": "or_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 15165.681869102991, + "annual_modeled_benefits": 33054937.630309362 + } + }, + { + "state": "PA", + "sample_households": 1999, + "baseline": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + }, + "no_modeled_irregular_hours": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 6, + "weighted_positive_units": 0.197240982321091, + "annual_modeled_benefits": 532.083619909163 + }, + "one_fewer_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 560.5847087540619 + }, + "one_more_day": { + "variable": "pa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 0.2005278345895931, + "annual_modeled_benefits": 561.099948525683 + } + }, + { + "state": "RI", + "sample_households": 804, + "baseline": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429808.7526231185 + }, + "candidate": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "no_modeled_irregular_hours": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_fewer_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + }, + "one_more_day": { + "variable": "ri_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 4049.67838506322, + "annual_modeled_benefits": 2429813.4984327066 + } + }, + { + "state": "SC", + "sample_households": 955, + "baseline": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "no_modeled_irregular_hours": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "one_fewer_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + }, + "one_more_day": { + "variable": "sc_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 11, + "weighted_positive_units": 27915.89938246878, + "annual_modeled_benefits": 77088809.79821464 + } + }, + { + "state": "SD", + "sample_households": 866, + "baseline": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809179.365034073 + }, + "no_modeled_irregular_hours": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 1592.1698410607642, + "annual_modeled_benefits": 2921857.242712675 + }, + "one_fewer_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809179.365034073 + }, + "one_more_day": { + "variable": "sd_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 2978.0845139123267, + "annual_modeled_benefits": 5809179.365034073 + } + }, + { + "state": "TN", + "sample_households": 1082, + "baseline": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178659.05015105 + }, + "no_modeled_irregular_hours": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 12, + "weighted_positive_units": 37178.86130036996, + "annual_modeled_benefits": 76918622.95069009 + }, + "one_fewer_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178659.05015105 + }, + "one_more_day": { + "variable": "tn_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 14, + "weighted_positive_units": 54836.32565583871, + "annual_modeled_benefits": 120178659.05015105 + } + }, + { + "state": "TX", + "sample_households": 1875, + "baseline": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 93309.70299506374, + "annual_modeled_benefits": 199602579.27443355 + }, + "no_modeled_irregular_hours": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 15, + "weighted_positive_units": 72034.10924506374, + "annual_modeled_benefits": 172158000.8966679 + }, + "one_fewer_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 93309.70299506374, + "annual_modeled_benefits": 199602579.27443355 + }, + "one_more_day": { + "variable": "tx_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 16, + "weighted_positive_units": 93309.70299506374, + "annual_modeled_benefits": 208180895.03846782 + } + }, + { + "state": "UT", + "sample_households": 861, + "baseline": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "candidate": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "no_modeled_irregular_hours": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "one_fewer_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + }, + "one_more_day": { + "variable": "ut_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 23851.249713434547, + "annual_modeled_benefits": 66497469.149597794 + } + }, + { + "state": "VT", + "sample_households": 496, + "baseline": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 78, + "weighted_positive_units": 32213.61921006818, + "annual_modeled_benefits": 526819840.50677323 + }, + "candidate": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 33402.23592408285, + "annual_modeled_benefits": 686206383.39497 + }, + "no_modeled_irregular_hours": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 33402.23592408285, + "annual_modeled_benefits": 686206383.39497 + }, + "one_fewer_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 33402.23592408285, + "annual_modeled_benefits": 686206383.39497 + }, + "one_more_day": { + "variable": "vt_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 81, + "weighted_positive_units": 33402.23592408285, + "annual_modeled_benefits": 686206383.39497 + } + }, + { + "state": "VA", + "sample_households": 1394, + "baseline": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 254330200.2860741 + }, + "no_modeled_irregular_hours": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 45187.34378196369, + "annual_modeled_benefits": 254330196.00231922 + }, + "one_fewer_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 254330157.19498563 + }, + "one_more_day": { + "variable": "va_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 9, + "weighted_positive_units": 45187.34717369359, + "annual_modeled_benefits": 254330254.14993468 + } + }, + { + "state": "WA", + "sample_households": 1305, + "baseline": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "candidate": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "no_modeled_irregular_hours": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_fewer_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + }, + "one_more_day": { + "variable": "wa_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 18, + "weighted_positive_units": 34613.59633511363, + "annual_modeled_benefits": 273947341.7692 + } + }, + { + "state": "WV", + "sample_households": 845, + "baseline": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 5322.572984872386, + "annual_modeled_benefits": 13548522.496824618 + }, + "no_modeled_irregular_hours": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 4, + "weighted_positive_units": 2978.909898934886, + "annual_modeled_benefits": 9329928.942137118 + }, + "one_fewer_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 5322.572984872386, + "annual_modeled_benefits": 13548523.617533846 + }, + "one_more_day": { + "variable": "wv_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 5, + "weighted_positive_units": 5322.572984872386, + "annual_modeled_benefits": 18821763.44399871 + } + }, + { + "state": "WI", + "sample_households": 1232, + "baseline": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "no_modeled_irregular_hours": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "one_fewer_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + }, + "one_more_day": { + "variable": "wi_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 8, + "weighted_positive_units": 33918.60478726821, + "annual_modeled_benefits": 249284653.88471013 + } + }, + { + "state": "WY", + "sample_households": 557, + "baseline": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 0, + "weighted_positive_units": 0.0, + "annual_modeled_benefits": 0.0 + }, + "candidate": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "no_modeled_irregular_hours": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.8060375964124, + "annual_modeled_benefits": 18103216.918334827 + }, + "one_fewer_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103301.12153925 + }, + "one_more_day": { + "variable": "wy_child_care_subsidies", + "entity": "spm_unit", + "positive_sample_units": 7, + "weighted_positive_units": 2962.834392214354, + "annual_modeled_benefits": 18103302.078379974 + } + } + ], + "production_ready": false, + "interpretation": "assumption stress tests, not confidence intervals; potential modeled benefits, not calibrated spending", + "summary": { + "states_evaluated": 51, + "annual_potential_modeled_benefits": { + "baseline": 2253603947.6976757, + "candidate": 6205696565.309278, + "no_modeled_irregular_hours": 5984831321.251355, + "one_fewer_day": 6203274049.118688, + "one_more_day": 6235237933.571062 + }, + "sensitivity_flags": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "SD", + "relative_change": -0.4970275388121817, + "absolute_change": -2887322.1223213975, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.35996437671524, + "absolute_change": -43260036.09946096, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WV", + "relative_change": -0.31136926965107936, + "absolute_change": -4218593.5546875, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_more_day", + "state": "WV", + "relative_change": 0.3892115135366226, + "absolute_change": 5273240.947174091, + "flagged": true + } + ], + "comparisons": [ + { + "scenario": "no_modeled_irregular_hours", + "state": "US", + "relative_change": -0.035590725671730446, + "absolute_change": -220865244.05792236, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AZ", + "relative_change": 0.09568548407951132, + "absolute_change": 2022606.8661100045, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "AR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CA", + "relative_change": -0.13145158522461736, + "absolute_change": -123012301.1687243, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "CT", + "relative_change": -4.8441717635025865e-08, + "absolute_change": -31.65541100502014, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "DC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "FL", + "relative_change": -0.051012249482424006, + "absolute_change": -16350408.75, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IL", + "relative_change": -0.009635512331510165, + "absolute_change": -1896136.3013213873, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "KY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "LA", + "relative_change": -0.0524188932261243, + "absolute_change": -3818315.91796875, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MS", + "relative_change": -1.343320763490555e-07, + "absolute_change": -4.090397037565708, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NM", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "ND", + "relative_change": -0.011297968792886796, + "absolute_change": -5.382686222510529, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "PA", + "relative_change": -0.05171329759120778, + "absolute_change": -29.016328616520013, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "SD", + "relative_change": -0.4970275388121817, + "absolute_change": -2887322.1223213975, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TN", + "relative_change": -0.35996437671524, + "absolute_change": -43260036.09946096, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "TX", + "relative_change": -0.13749611090962963, + "absolute_change": -27444578.377765656, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "VA", + "relative_change": -1.68432804298438e-08, + "absolute_change": -4.283754885196686, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WV", + "relative_change": -0.31136926965107936, + "absolute_change": -4218593.5546875, + "flagged": true + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "no_modeled_irregular_hours", + "state": "WY", + "relative_change": -4.651262433181563e-06, + "absolute_change": -84.20320442318916, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "US", + "relative_change": -0.0003903697457803711, + "absolute_change": -2422516.190589905, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AZ", + "relative_change": -1.5313118205487516e-05, + "absolute_change": -323.6898294650018, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "AR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "DC", + "relative_change": -4.786141643741066e-10, + "absolute_change": -0.011446915566921234, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "FL", + "relative_change": -0.007553250927829238, + "absolute_change": -2420962.4416558146, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IL", + "relative_change": -6.484094955654378e-07, + "absolute_change": -127.59807059168816, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "KY", + "relative_change": 0.03271163561094901, + "absolute_change": 5.506053298496624, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "LA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MO", + "relative_change": 3.3244676879557186e-08, + "absolute_change": 1.4664233028888702, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NM", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NY", + "relative_change": -2.757692892404338e-06, + "absolute_change": -1064.8765941262245, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "ND", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "OK", + "relative_change": -0.40000005159976976, + "absolute_change": -2.0598506164868837, + "flagged": true + }, + { + "scenario": "one_fewer_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "PA", + "relative_change": -0.000918267365689253, + "absolute_change": -0.5152397716210544, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "SD", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "TX", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "VA", + "relative_change": -1.6942969582584922e-07, + "absolute_change": -43.091088473796844, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WV", + "relative_change": 8.27181877330683e-08, + "absolute_change": 1.120709227398038, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_fewer_day", + "state": "WY", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "US", + "relative_change": 0.004760362990824428, + "absolute_change": 29541368.261784554, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AZ", + "relative_change": -0.04254747069772985, + "absolute_change": -899371.5943092518, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "AR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CO", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "CT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "DC", + "relative_change": -5.4787842155856995e-09, + "absolute_change": -0.1310349442064762, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "FL", + "relative_change": 0.05175373503634142, + "absolute_change": 16588069.155329645, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "GA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "HI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ID", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IL", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "IA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "KY", + "relative_change": -0.026169311446625897, + "absolute_change": -4.404843136668205, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "LA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ME", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MD", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MS", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MO", + "relative_change": -2.6595738767319386e-08, + "absolute_change": -1.1731385216116905, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "MT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NE", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NV", + "relative_change": null, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NJ", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NM", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NY", + "relative_change": 2.7576961982711995e-06, + "absolute_change": 1064.8778706789017, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "NC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "ND", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OH", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OK", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "OR", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "PA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "RI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SC", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "SD", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TN", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "TX", + "relative_change": 0.04297697853012183, + "absolute_change": 8578315.764034271, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "UT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VT", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "VA", + "relative_change": 2.117871197237217e-07, + "absolute_change": 53.863860577344894, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WA", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WV", + "relative_change": 0.3892115135366226, + "absolute_change": 5273240.947174091, + "flagged": true + }, + { + "scenario": "one_more_day", + "state": "WI", + "relative_change": 0.0, + "absolute_change": 0.0, + "flagged": false + }, + { + "scenario": "one_more_day", + "state": "WY", + "relative_change": 5.285448865536073e-08, + "absolute_change": 0.9568407237529755, + "flagged": false + } + ] + } +} diff --git a/experiments/us-childcare-attendance/review-fixes-source-stage.json b/experiments/us-childcare-attendance/review-fixes-source-stage.json new file mode 100644 index 000000000..a4e30b8f1 --- /dev/null +++ b/experiments/us-childcare-attendance/review-fixes-source-stage.json @@ -0,0 +1,2013 @@ +{ + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "source_child_count": 11745, + "source_attrition": [ + { + "status": "age_out_of_scope", + "n": 134, + "child_weight": 633390.6468148227 + }, + { + "status": "ambiguous_calendar", + "n": 882, + "child_weight": 3918044.035953992 + }, + { + "status": "complete", + "n": 7460, + "child_weight": 32249785.682044942 + }, + { + "status": "missing_calendar", + "n": 3095, + "child_weight": 14061828.648228496 + }, + { + "status": "partial_calendar", + "n": 174, + "child_weight": 847769.9869632281 + } + ], + "under13_complete_weight_share": 0.6313901604255374, + "seed": 915, + "evaluation_design": "fixed household diagnostic split; not final population certification", + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "training_children": 5988, + "heldout_children": 1472, + "unsupported_heldout_children": 30, + "unsupported_heldout_weight": 153817.788204796, + "household_overlap": 0, + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1442, + "observed": { + "participation": 0.47956011362829676, + "days_per_week": 1.94220990502671, + "hours_per_week": 14.670259928481958 + }, + "predicted": { + "participation": 0.4547372440086608, + "days_per_week": 1.7986704844660344, + "hours_per_week": 13.001295500494344 + } + }, + { + "grouping": "age", + "group": "0", + "n": 87, + "observed": { + "participation": 0.4749769477860246, + "days_per_week": 1.8705229048944185, + "hours_per_week": 15.234832555457738 + }, + "predicted": { + "participation": 0.21347668238234924, + "days_per_week": 0.7762397588546358, + "hours_per_week": 6.331944692101247 + } + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed": { + "participation": 0.6026124718926947, + "days_per_week": 2.2004884644301415, + "hours_per_week": 16.058889950232714 + }, + "predicted": { + "participation": 0.5817111215090339, + "days_per_week": 2.3867359768549528, + "hours_per_week": 20.29503642359935 + } + }, + { + "grouping": "age", + "group": "2", + "n": 107, + "observed": { + "participation": 0.6827728698846081, + "days_per_week": 3.2446932739752876, + "hours_per_week": 32.643365116161064 + }, + "predicted": { + "participation": 0.721108922787169, + "days_per_week": 2.9279814704204314, + "hours_per_week": 25.4484971084278 + } + }, + { + "grouping": "age", + "group": "3", + "n": 105, + "observed": { + "participation": 0.5478113606887365, + "days_per_week": 2.4737318877881727, + "hours_per_week": 18.774571500322175 + }, + "predicted": { + "participation": 0.7525857598038783, + "days_per_week": 3.556893850206531, + "hours_per_week": 31.34897414405891 + } + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed": { + "participation": 0.6853382748904278, + "days_per_week": 3.133535876664359, + "hours_per_week": 22.72675588135229 + }, + "predicted": { + "participation": 0.6264622224091229, + "days_per_week": 2.555534029105278, + "hours_per_week": 22.558086951188297 + } + }, + { + "grouping": "age", + "group": "5", + "n": 109, + "observed": { + "participation": 0.45965467004039157, + "days_per_week": 2.204288116474054, + "hours_per_week": 16.671100530443887 + }, + "predicted": { + "participation": 0.5197893305690322, + "days_per_week": 2.0628456061601703, + "hours_per_week": 14.660711473842793 + } + }, + { + "grouping": "age", + "group": "6", + "n": 121, + "observed": { + "participation": 0.37767600901948256, + "days_per_week": 1.4233675361325482, + "hours_per_week": 11.368361596236443 + }, + "predicted": { + "participation": 0.3812603191662228, + "days_per_week": 1.344172188177959, + "hours_per_week": 6.687190574415363 + } + }, + { + "grouping": "age", + "group": "7", + "n": 121, + "observed": { + "participation": 0.2903318905157446, + "days_per_week": 0.9137564001326794, + "hours_per_week": 4.425088819370026 + }, + "predicted": { + "participation": 0.3316072853951072, + "days_per_week": 1.3098426672238894, + "hours_per_week": 7.381236186215511 + } + }, + { + "grouping": "age", + "group": "8", + "n": 136, + "observed": { + "participation": 0.33161676756925296, + "days_per_week": 1.2251387657453359, + "hours_per_week": 6.71788070980216 + }, + "predicted": { + "participation": 0.35465278512495324, + "days_per_week": 1.3642638058954168, + "hours_per_week": 9.725344329820171 + } + }, + { + "grouping": "age", + "group": "9", + "n": 101, + "observed": { + "participation": 0.4569510479920196, + "days_per_week": 1.674346958462562, + "hours_per_week": 7.805739183708693 + }, + "predicted": { + "participation": 0.396876880067868, + "days_per_week": 1.3966673442424729, + "hours_per_week": 8.701466574792914 + } + }, + { + "grouping": "age", + "group": "10", + "n": 105, + "observed": { + "participation": 0.45503949077471245, + "days_per_week": 1.5782906105470733, + "hours_per_week": 12.426848636692846 + }, + "predicted": { + "participation": 0.17752112044794535, + "days_per_week": 0.6469841431239514, + "hours_per_week": 3.596759032644189 + } + }, + { + "grouping": "age", + "group": "11", + "n": 119, + "observed": { + "participation": 0.41662598750149954, + "days_per_week": 1.8643111020323588, + "hours_per_week": 12.941021091087745 + }, + "predicted": { + "participation": 0.4008574857176407, + "days_per_week": 1.339718142783517, + "hours_per_week": 7.024111200026524 + } + }, + { + "grouping": "age", + "group": "12", + "n": 103, + "observed": { + "participation": 0.46408861423609865, + "days_per_week": 1.4945247924023801, + "hours_per_week": 11.114322042194766 + }, + "predicted": { + "participation": 0.43591150819042584, + "days_per_week": 1.7008936429461807, + "hours_per_week": 7.10555160431239 + } + }, + { + "grouping": "region", + "group": "1", + "n": 187, + "observed": { + "participation": 0.5340539220646277, + "days_per_week": 2.4009930157522064, + "hours_per_week": 14.79000641069395 + }, + "predicted": { + "participation": 0.6359603067185889, + "days_per_week": 2.7160385976574153, + "hours_per_week": 21.24487930853725 + } + }, + { + "grouping": "region", + "group": "2", + "n": 335, + "observed": { + "participation": 0.46285701128880247, + "days_per_week": 1.8453473655274784, + "hours_per_week": 15.352184287431765 + }, + "predicted": { + "participation": 0.41283532735257467, + "days_per_week": 1.4868890394545802, + "hours_per_week": 9.329182591248077 + } + }, + { + "grouping": "region", + "group": "3", + "n": 470, + "observed": { + "participation": 0.4328632075166446, + "days_per_week": 1.8236781494293404, + "hours_per_week": 15.35364160426729 + }, + "predicted": { + "participation": 0.4184231306502971, + "days_per_week": 1.5947483523709618, + "hours_per_week": 10.342311458512322 + } + }, + { + "grouping": "region", + "group": "4", + "n": 450, + "observed": { + "participation": 0.5171827642556369, + "days_per_week": 1.8475690827448323, + "hours_per_week": 13.04393396272379 + }, + "predicted": { + "participation": 0.4063739455351018, + "days_per_week": 1.670368549893466, + "hours_per_week": 13.748230642009998 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 13, + "observed": { + "participation": 0.253295875991658, + "days_per_week": 0.8671327195533685, + "hours_per_week": 3.515415042668932 + }, + "predicted": { + "participation": 0.12949135028799522, + "days_per_week": 0.5134069370080796, + "hours_per_week": 3.2849650228565768 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 150, + "observed": { + "participation": 0.2939873489805854, + "days_per_week": 1.1857568669020946, + "hours_per_week": 10.001769026104405 + }, + "predicted": { + "participation": 0.20052896295212805, + "days_per_week": 0.7861821143968067, + "hours_per_week": 6.547539835013359 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 543, + "observed": { + "participation": 0.2512596429034898, + "days_per_week": 0.8664048151368835, + "hours_per_week": 5.612021341518408 + }, + "predicted": { + "participation": 0.30234830539829555, + "days_per_week": 0.9663278582430083, + "hours_per_week": 5.557806485223704 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 736, + "observed": { + "participation": 0.6462583482271946, + "days_per_week": 2.714084969528804, + "hours_per_week": 21.02161873348801 + }, + "predicted": { + "participation": 0.5856324643226721, + "days_per_week": 2.4572831629804637, + "hours_per_week": 18.54158986890966 + } + } + ], + "production_ready": false, + "limitations": [ + "Only complete, unambiguous calendars enter this holdout; source selection remains unvalidated.", + "The diagnostic matches age and caller-specified covariates; it is not a nationally validated fitted model.", + "Sibling assignments, mixed providers, older-child care and scalar monthly conversion need validation.", + "No full Microcosm population build or state CCDF distribution is certified by this source-only report." + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "selection_and_cross_validation": { + "source": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ] + }, + "seed": 271828, + "design": "five-fold household-separated conditional-expectation diagnostic", + "coverage": [ + { + "grouping": "questionnaire_version", + "group": "1", + "n": 8516, + "weight": 37015599.704962164, + "usable_weight_share": 0.8712484989868113 + }, + { + "grouping": "questionnaire_version", + "group": "2", + "n": 1716, + "weight": 7945306.22844611, + "usable_weight_share": 0.0 + }, + { + "grouping": "questionnaire_version", + "group": "3", + "n": 1379, + "weight": 6116522.419782387, + "usable_weight_share": 0.0 + }, + { + "grouping": "age", + "group": "0", + "n": 709, + "weight": 3159547.2696341733, + "usable_weight_share": 0.6556513621184303 + }, + { + "grouping": "age", + "group": "1", + "n": 856, + "weight": 3710311.739747545, + "usable_weight_share": 0.6518631977426506 + }, + { + "grouping": "age", + "group": "2", + "n": 906, + "weight": 4112848.861546397, + "usable_weight_share": 0.6567885484115844 + }, + { + "grouping": "age", + "group": "3", + "n": 927, + "weight": 4191268.180093972, + "usable_weight_share": 0.5836151261043054 + }, + { + "grouping": "age", + "group": "4", + "n": 890, + "weight": 3761621.476875998, + "usable_weight_share": 0.6069150244376095 + }, + { + "grouping": "age", + "group": "5", + "n": 884, + "weight": 3759953.5032736305, + "usable_weight_share": 0.6441250249176611 + }, + { + "grouping": "age", + "group": "6", + "n": 954, + "weight": 4259102.717846392, + "usable_weight_share": 0.6213110691400029 + }, + { + "grouping": "age", + "group": "7", + "n": 944, + "weight": 4191490.05150999, + "usable_weight_share": 0.6014263712726516 + }, + { + "grouping": "age", + "group": "8", + "n": 925, + "weight": 3969583.419307067, + "usable_weight_share": 0.6776062606911202 + }, + { + "grouping": "age", + "group": "9", + "n": 921, + "weight": 4036325.188020362, + "usable_weight_share": 0.6661696977311178 + }, + { + "grouping": "age", + "group": "10", + "n": 884, + "weight": 3707790.894951107, + "usable_weight_share": 0.6220814618665044 + }, + { + "grouping": "age", + "group": "11", + "n": 908, + "weight": 4069233.422108399, + "usable_weight_share": 0.6084497314858798 + }, + { + "grouping": "age", + "group": "12", + "n": 903, + "weight": 4148351.6282756263, + "usable_weight_share": 0.6217216150291205 + }, + { + "grouping": "region", + "group": "1", + "n": 1468, + "weight": 8153076.12536276, + "usable_weight_share": 0.6401722331669089 + }, + { + "grouping": "region", + "group": "2", + "n": 2680, + "weight": 10714972.208999828, + "usable_weight_share": 0.6330942004076335 + }, + { + "grouping": "region", + "group": "3", + "n": 4275, + "weight": 20022339.81304313, + "usable_weight_share": 0.6249460927360144 + }, + { + "grouping": "region", + "group": "4", + "n": 3188, + "weight": 12187040.205784943, + "usable_weight_share": 0.634603874645437 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 188, + "weight": 819575.738509818, + "usable_weight_share": 0.646736557730207 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1470, + "weight": 5301911.400981113, + "usable_weight_share": 0.6698890755679009 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 3380, + "weight": 14009836.058523985, + "usable_weight_share": 0.7212981495115555 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 6573, + "weight": 30946105.155175745, + "usable_weight_share": 0.5836849144158337 + }, + { + "grouping": "income_band", + "group": "0", + "n": 120, + "weight": 497291.815717528, + "usable_weight_share": 0.602001589054288 + }, + { + "grouping": "income_band", + "group": "1", + "n": 2057, + "weight": 7466809.932490364, + "usable_weight_share": 0.6413274998297077 + }, + { + "grouping": "income_band", + "group": "2", + "n": 2904, + "weight": 10880663.16706181, + "usable_weight_share": 0.6487048847817203 + }, + { + "grouping": "income_band", + "group": "3", + "n": 2558, + "weight": 11325516.809686286, + "usable_weight_share": 0.6401496463974632 + }, + { + "grouping": "income_band", + "group": "4", + "n": 3134, + "weight": 15757011.988844048, + "usable_weight_share": 0.6309648019074636 + }, + { + "grouping": "income_band", + "group": "5", + "n": 838, + "weight": 5150134.639390623, + "usable_weight_share": 0.5652783862122169 + } + ], + "cross_validation": [ + { + "grouping": "all", + "group": "all", + "n": 7460, + "observed": { + "participation": 0.46514845107061753, + "days": 1.8750709563867467, + "hours": 14.240422572045741 + }, + "expected": { + "participation": 0.4654844290416027, + "days": 1.878527294011518, + "hours": 14.081114700859393 + } + }, + { + "grouping": "age", + "group": "0", + "n": 458, + "observed": { + "participation": 0.40007662898763713, + "days": 1.5207410031354076, + "hours": 12.391341719561105 + }, + "expected": { + "participation": 0.42043937266485903, + "days": 1.5581186938006875, + "hours": 12.778280226573155 + } + }, + { + "grouping": "age", + "group": "1", + "n": 585, + "observed": { + "participation": 0.5768442364096245, + "days": 2.3247221464109487, + "hours": 18.93194004904792 + }, + "expected": { + "participation": 0.5636534387398192, + "days": 2.2787632897750942, + "hours": 18.46136873433321 + } + }, + { + "grouping": "age", + "group": "2", + "n": 599, + "observed": { + "participation": 0.6119472630516792, + "days": 2.661365110302835, + "hours": 23.568671427585098 + }, + "expected": { + "participation": 0.615171451571855, + "days": 2.6668583554129937, + "hours": 24.008204825875836 + } + }, + { + "grouping": "age", + "group": "3", + "n": 597, + "observed": { + "participation": 0.6106570906701301, + "days": 2.6641302585227766, + "hours": 21.96346327818111 + }, + "expected": { + "participation": 0.6452189769582379, + "days": 2.8385499518769803, + "hours": 23.234965023661523 + } + }, + { + "grouping": "age", + "group": "4", + "n": 550, + "observed": { + "participation": 0.7046928755645677, + "days": 3.1467359328380096, + "hours": 25.30894736806148 + }, + "expected": { + "participation": 0.7168364294128767, + "days": 3.1704359925035326, + "hours": 25.23962440956216 + } + }, + { + "grouping": "age", + "group": "5", + "n": 581, + "observed": { + "participation": 0.5580840489623686, + "days": 2.527957605747102, + "hours": 20.351246817207983 + }, + "expected": { + "participation": 0.5580783794454302, + "days": 2.4817744855985993, + "hours": 19.696005730111843 + } + }, + { + "grouping": "age", + "group": "6", + "n": 611, + "observed": { + "participation": 0.38410943637037254, + "days": 1.5233703763582376, + "hours": 9.272995362839891 + }, + "expected": { + "participation": 0.3704517476571586, + "days": 1.4435320487985335, + "hours": 8.308626409800924 + } + }, + { + "grouping": "age", + "group": "7", + "n": 586, + "observed": { + "participation": 0.34047335603475937, + "days": 1.267585660002729, + "hours": 8.978033563109225 + }, + "expected": { + "participation": 0.32048663598972876, + "days": 1.2092330340598734, + "hours": 7.976819608666644 + } + }, + { + "grouping": "age", + "group": "8", + "n": 611, + "observed": { + "participation": 0.37000227568041516, + "days": 1.4234519706143929, + "hours": 8.822905923973602 + }, + "expected": { + "participation": 0.37441818500322505, + "days": 1.4678570808250095, + "hours": 8.581931573672641 + } + }, + { + "grouping": "age", + "group": "9", + "n": 590, + "observed": { + "participation": 0.3862968300759907, + "days": 1.373131779820256, + "hours": 9.825473717875598 + }, + "expected": { + "participation": 0.3658218612710925, + "days": 1.2745107095149821, + "hours": 9.212380152023707 + } + }, + { + "grouping": "age", + "group": "10", + "n": 541, + "observed": { + "participation": 0.39185689727904155, + "days": 1.3647219850376735, + "hours": 8.468134791861964 + }, + "expected": { + "participation": 0.3879904743599543, + "days": 1.313066433397897, + "hours": 7.6091158562940935 + } + }, + { + "grouping": "age", + "group": "11", + "n": 565, + "observed": { + "participation": 0.3768238596848206, + "days": 1.3633597325953704, + "hours": 8.143708694787577 + }, + "expected": { + "participation": 0.37027421084947587, + "days": 1.3896706621501198, + "hours": 8.180743029734462 + } + }, + { + "grouping": "age", + "group": "12", + "n": 586, + "observed": { + "participation": 0.35838594666001994, + "days": 1.3239431239565607, + "hours": 10.203528792841063 + }, + "expected": { + "participation": 0.37173621674013835, + "days": 1.447846572682685, + "hours": 10.94364602719194 + } + }, + { + "grouping": "region", + "group": "1", + "n": 929, + "observed": { + "participation": 0.4988625902951673, + "days": 2.101321934458425, + "hours": 14.697745432365767 + }, + "expected": { + "participation": 0.5341920187102704, + "days": 2.234373766910041, + "hours": 15.515545077772881 + } + }, + { + "grouping": "region", + "group": "2", + "n": 1654, + "observed": { + "participation": 0.495642471526947, + "days": 1.9378635600824832, + "hours": 14.438707404977198 + }, + "expected": { + "participation": 0.48657470779470846, + "days": 1.9053160452954483, + "hours": 13.830397580793818 + } + }, + { + "grouping": "region", + "group": "3", + "n": 2752, + "observed": { + "participation": 0.4310162776090592, + "days": 1.7482660126296112, + "hours": 14.16760866653331 + }, + "expected": { + "participation": 0.4254047590539008, + "days": 1.728432288982956, + "hours": 13.771990873343658 + } + }, + { + "grouping": "region", + "group": "4", + "n": 2125, + "observed": { + "participation": 0.470872102608504, + "days": 1.872465315119329, + "hours": 13.875678601794997 + }, + "expected": { + "participation": 0.46546295898430057, + "days": 1.857723153971244, + "hours": 13.833112421687238 + } + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 122, + "observed": { + "participation": 0.34614706740243706, + "days": 1.1849281571656916, + "hours": 9.788566562075097 + }, + "expected": { + "participation": 0.373389275855184, + "days": 1.462386368172592, + "hours": 12.574282386813668 + } + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 1016, + "observed": { + "participation": 0.3096578455166947, + "days": 1.244408501915661, + "hours": 10.193456359940383 + }, + "expected": { + "participation": 0.2882996076692788, + "days": 1.1762845771341546, + "hours": 10.282450861819337 + } + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 2418, + "observed": { + "participation": 0.2864480974767621, + "days": 0.9529380212414282, + "hours": 6.42952602558992 + }, + "expected": { + "participation": 0.29059369126489437, + "days": 0.9858655333196232, + "hours": 6.764091600272923 + } + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 3904, + "observed": { + "participation": 0.5991891288528669, + "days": 2.535220374688929, + "hours": 19.536646150926547 + }, + "expected": { + "participation": 0.6008699918772364, + "days": 2.528223299633135, + "hours": 18.96579429859326 + } + }, + { + "grouping": "income_band", + "group": "0", + "n": 76, + "observed": { + "participation": 0.19070472825408744, + "days": 0.8431547354115522, + "hours": 5.587881885800679 + }, + "expected": { + "participation": 0.25300464764173586, + "days": 0.9988467887623982, + "hours": 7.43593331362669 + } + }, + { + "grouping": "income_band", + "group": "1", + "n": 1378, + "observed": { + "participation": 0.35684525807866907, + "days": 1.5280931021110342, + "hours": 12.098088591903252 + }, + "expected": { + "participation": 0.3773650597658377, + "days": 1.627338456412184, + "hours": 12.706694579809993 + } + }, + { + "grouping": "income_band", + "group": "2", + "n": 1922, + "observed": { + "participation": 0.4194011939997811, + "days": 1.7068232976926216, + "hours": 13.491053608546364 + }, + "expected": { + "participation": 0.4119472083085292, + "days": 1.7030609232530567, + "hours": 13.262000601507708 + } + }, + { + "grouping": "income_band", + "group": "3", + "n": 1632, + "observed": { + "participation": 0.4246978564102466, + "days": 1.7238735833385048, + "hours": 14.076591113511121 + }, + "expected": { + "participation": 0.4255788471352766, + "days": 1.704797441331804, + "hours": 13.264862389978802 + } + }, + { + "grouping": "income_band", + "group": "4", + "n": 1977, + "observed": { + "participation": 0.5221816116808028, + "days": 1.9968956951019248, + "hours": 14.35832226780555 + }, + "expected": { + "participation": 0.519705526319959, + "days": 1.9988673657729517, + "hours": 14.55106672367201 + } + }, + { + "grouping": "income_band", + "group": "5", + "n": 475, + "observed": { + "participation": 0.6883939484468758, + "days": 2.9203317046618578, + "hours": 20.476264713601847 + }, + "expected": { + "participation": 0.6762909354194406, + "days": 2.8292574855634296, + "hours": 19.43897806431697 + } + } + ], + "matching_levels": { + "age,region,parent_work_status,income_band": 7278, + "age,parent_work_status,income_band": 150, + "age,parent_work_status": 32 + }, + "sibling_validation": [ + { + "fold": 0, + "training_rho": 0.7504775690168417, + "households": 351, + "weight": 1627200.3809817089, + "observed_both": 0.3346110552302055, + "independent_both": 0.26023741618799584, + "coupled_both": 0.3228394147816889 + }, + { + "fold": 1, + "training_rho": 0.8095401305423818, + "households": 367, + "weight": 1640369.17044373, + "observed_both": 0.308478237709926, + "independent_both": 0.2538314963354507, + "coupled_both": 0.31777192913378116 + }, + { + "fold": 2, + "training_rho": 0.7176223341350483, + "households": 443, + "weight": 1849383.6729044288, + "observed_both": 0.3831182075641927, + "independent_both": 0.260592140353083, + "coupled_both": 0.3144997720634755 + }, + { + "fold": 3, + "training_rho": 0.8946850037821993, + "households": 398, + "weight": 1567825.4608580691, + "observed_both": 0.2954259187077949, + "independent_both": 0.2551329149477867, + "coupled_both": 0.3186981148017382 + }, + { + "fold": 4, + "training_rho": 0.8022505236076816, + "households": 382, + "weight": 1644777.832273697, + "observed_both": 0.3129856591731878, + "independent_both": 0.2695651435301845, + "coupled_both": 0.3411406780146054 + } + ], + "questionnaire_transport": [ + { + "questionnaire_version": 2, + "scored_children": 1690, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.4166998042800092, + "regular_hours_per_week": 12.379208437570716 + }, + "expected": { + "regular_participation": 0.429264128691119, + "regular_hours_per_week": 14.800674181069196 + } + }, + { + "questionnaire_version": 3, + "scored_children": 1356, + "unsupported_children": 0, + "observed": { + "regular_participation": 0.38447915314763226, + "regular_hours_per_week": 10.163827277430636 + }, + "expected": { + "regular_participation": 0.40786334693307874, + "regular_hours_per_week": 13.921490221932158 + } + } + ], + "assumptions": [ + "Calendar selection is ignorable conditional on matching fields; not identified from these data.", + "Main reference-week schedules transfer to May/fall typical weeks; summer attendance is not observed.", + "Regular-care comparisons cannot validate days or irregular care in instruments without calendars." + ], + "production_ready": false + }, + "masked_calendar_validation": { + "seed": 161803, + "design": "whole-household masked-calendar test; regular weekly hours remain observed", + "comparisons": [ + { + "grouping": "all", + "group": "all", + "n": 1581, + "observed_days": 1.7176113066572272, + "imputed_days": 1.8010008506568833, + "observed_hours": 13.097316141278531, + "imputed_hours": 13.88548689950872 + }, + { + "grouping": "age", + "group": "0", + "n": 104, + "observed_days": 1.841126169101498, + "imputed_days": 1.7155709258903193, + "observed_hours": 17.766331967357488, + "imputed_hours": 18.060021757471628 + }, + { + "grouping": "age", + "group": "1", + "n": 113, + "observed_days": 2.37969546844872, + "imputed_days": 2.238095942159061, + "observed_hours": 19.829328945193588, + "imputed_hours": 19.1108645563332 + }, + { + "grouping": "age", + "group": "2", + "n": 141, + "observed_days": 2.4591773765558673, + "imputed_days": 2.513649003783342, + "observed_hours": 22.536051090399965, + "imputed_hours": 21.954590139823193 + }, + { + "grouping": "age", + "group": "3", + "n": 123, + "observed_days": 2.3763627081083216, + "imputed_days": 2.4413645269610935, + "observed_hours": 20.251977914853146, + "imputed_hours": 20.142661302712924 + }, + { + "grouping": "age", + "group": "4", + "n": 115, + "observed_days": 3.137397964036117, + "imputed_days": 3.3032151052457945, + "observed_hours": 23.68360614421261, + "imputed_hours": 25.45979594267697 + }, + { + "grouping": "age", + "group": "5", + "n": 113, + "observed_days": 2.2242008794879244, + "imputed_days": 2.8874743140551375, + "observed_hours": 15.760856040214335, + "imputed_hours": 24.9499438984932 + }, + { + "grouping": "age", + "group": "6", + "n": 130, + "observed_days": 1.1830651060264654, + "imputed_days": 1.5127783403894393, + "observed_hours": 7.042175416036001, + "imputed_hours": 7.241980015558179 + }, + { + "grouping": "age", + "group": "7", + "n": 131, + "observed_days": 1.4073709771017413, + "imputed_days": 1.2667237724301017, + "observed_hours": 10.083939201157254, + "imputed_hours": 9.891690761289606 + }, + { + "grouping": "age", + "group": "8", + "n": 126, + "observed_days": 1.057579033652681, + "imputed_days": 1.0242609251134895, + "observed_hours": 8.256788366496435, + "imputed_hours": 6.971083491156134 + }, + { + "grouping": "age", + "group": "9", + "n": 118, + "observed_days": 1.4349301372689758, + "imputed_days": 1.20915603900853, + "observed_hours": 10.733568940718998, + "imputed_hours": 9.968682065794441 + }, + { + "grouping": "age", + "group": "10", + "n": 124, + "observed_days": 1.2058459898990705, + "imputed_days": 1.2184622017593358, + "observed_hours": 4.1774445652404655, + "imputed_hours": 4.378598134956982 + }, + { + "grouping": "age", + "group": "11", + "n": 120, + "observed_days": 0.9483350107584563, + "imputed_days": 1.2340597164150218, + "observed_hours": 5.566349482238208, + "imputed_hours": 5.986332786079906 + }, + { + "grouping": "age", + "group": "12", + "n": 123, + "observed_days": 1.1181905452629102, + "imputed_days": 1.271942733975096, + "observed_hours": 8.079018679724042, + "imputed_hours": 11.093992734455059 + }, + { + "grouping": "parent_work_status", + "group": "-1", + "n": 33, + "observed_days": 1.5430497710033617, + "imputed_days": 1.847082458214411, + "observed_hours": 12.754941207431992, + "imputed_hours": 14.212849249137319 + }, + { + "grouping": "parent_work_status", + "group": "0", + "n": 233, + "observed_days": 1.3163214930511598, + "imputed_days": 1.4883107558373656, + "observed_hours": 9.57367968242547, + "imputed_hours": 10.147531300541837 + }, + { + "grouping": "parent_work_status", + "group": "1", + "n": 529, + "observed_days": 0.8935323002915262, + "imputed_days": 1.044203922701497, + "observed_hours": 6.370457810059637, + "imputed_hours": 8.615629262680223 + }, + { + "grouping": "parent_work_status", + "group": "2", + "n": 786, + "observed_days": 2.3152602893352414, + "imputed_days": 2.3300691025449733, + "observed_hours": 17.994069548324816, + "imputed_hours": 17.92138056591039 + } + ], + "regular_hours_preserved": true, + "limitations": "Tests reconstruction where calendars exist; not a direct test of unobserved days in the other questionnaire instruments." + }, + "sibling_schedule_validation": { + "design": "household-separated partitions; exact integration of the implemented shared-rank donor CDFs", + "seed": 271828, + "validation_seed": null, + "partition": "all", + "splits": [ + { + "training_households": 5100, + "evaluation_households": 1269, + "household_overlap": 0 + }, + { + "training_households": 5108, + "evaluation_households": 1261, + "household_overlap": 0 + }, + { + "training_households": 5055, + "evaluation_households": 1314, + "household_overlap": 0 + }, + { + "training_households": 5071, + "evaluation_households": 1298, + "household_overlap": 0 + }, + { + "training_households": 5142, + "evaluation_households": 1227, + "household_overlap": 0 + } + ], + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "matching_counts": { + "age,region,parent_work_status,income_band": 4682, + "age,parent_work_status,income_band": 87, + "age,parent_work_status": 20 + }, + "training_rho": [ + 0.7504775690168417, + 0.8095401305423818, + 0.7176223341350483, + 0.8946850037821993, + 0.8022505236076816 + ], + "youngest_pairs": { + "households": 1941, + "household_weight": 8329556.517461634, + "observed": { + "participation": { + "joint_product": 0.3285886986591048, + "correlation": 0.577510777815278 + }, + "days": { + "joint_product": 5.9171615700449145, + "correlation": 0.5798886366650429 + }, + "weekly_hours": { + "joint_product": 367.6804958137289, + "correlation": 0.523457187358221 + } + }, + "independent": { + "participation": { + "joint_product": 0.2599357218299179, + "correlation": 0.14356484664250233 + }, + "days": { + "joint_product": 4.562554958882675, + "correlation": 0.1514064619097721 + }, + "weekly_hours": { + "joint_product": 274.5482618143516, + "correlation": 0.1290575794049498 + } + }, + "coupled": { + "participation": { + "joint_product": 0.3228241594928079, + "correlation": 0.39662161163709453 + }, + "days": { + "joint_product": 6.168391228908872, + "correlation": 0.44224735414975214 + }, + "weekly_hours": { + "joint_product": 393.50536137310974, + "correlation": 0.35635277128820114 + } + } + }, + "youngest_pairs_in_3plus_households": { + "households": 661, + "household_weight": 2458669.150522002, + "observed": { + "participation": { + "joint_product": 0.2728957065164463, + "correlation": 0.5341073068866067 + }, + "days": { + "joint_product": 5.084876229689592, + "correlation": 0.5617680576612079 + }, + "weekly_hours": { + "joint_product": 337.34705105325276, + "correlation": 0.5623528715201395 + } + }, + "independent": { + "participation": { + "joint_product": 0.26594831103314626, + "correlation": 0.1768906778018396 + }, + "days": { + "joint_product": 4.755017625871223, + "correlation": 0.1796772530434437 + }, + "weekly_hours": { + "joint_product": 288.78160178706247, + "correlation": 0.15022942733210107 + } + }, + "coupled": { + "participation": { + "joint_product": 0.32461663032314914, + "correlation": 0.41320572283269663 + }, + "days": { + "joint_product": 6.243213912824172, + "correlation": 0.44765187465853035 + }, + "weekly_hours": { + "joint_product": 406.53469377347056, + "correlation": 0.3715598543103152 + } + } + }, + "larger_households": { + "households": 661, + "observed": { + "mean_total_days": 4.509647234214243, + "mean_total_weekly_hours": 33.085374473048084, + "sd_total_days": 5.765302178946849, + "sd_total_weekly_hours": 56.21651776123905, + "all_children_attend": 0.18568592787006075 + }, + "independent": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 4.742171858563125, + "sd_total_weekly_hours": 43.69183090611876, + "all_children_attend": 0.10870430564775237 + }, + "coupled": { + "mean_total_days": 5.652805180994538, + "mean_total_weekly_hours": 40.814235235209445, + "sd_total_days": 5.826012333228911, + "sd_total_weekly_hours": 52.0732028760817, + "all_children_attend": 0.19850142013240785 + } + }, + "production_ready": false, + "diagnostic_screen": { + "passed": false, + "checks": [ + { + "metric": "youngest_pairs.participation.joint_product", + "absolute_gap": 0.005764539166296878, + "relative": false, + "limit": 0.05, + "passed": true + }, + { + "metric": "youngest_pairs.days.correlation", + "absolute_gap": 0.13764128251529073, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.weekly_hours.correlation", + "absolute_gap": 0.16710441607001986, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs.days.joint_product", + "absolute_gap": 0.04245779938404666, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs.weekly_hours.joint_product", + "absolute_gap": 0.07023724634135614, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "youngest_pairs_in_3plus_households.participation.joint_product", + "absolute_gap": 0.051720923806702845, + "relative": false, + "limit": 0.05, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.correlation", + "absolute_gap": 0.11411618300267751, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.correlation", + "absolute_gap": 0.19079301720982428, + "relative": false, + "limit": 0.1, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.days.joint_product", + "absolute_gap": 0.22780056599436455, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "youngest_pairs_in_3plus_households.weekly_hours.joint_product", + "absolute_gap": 0.20509336751041, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_days", + "absolute_gap": 0.2534916563112233, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.mean_total_weekly_hours", + "absolute_gap": 0.23360354492760613, + "relative": true, + "limit": 0.2, + "passed": false + }, + { + "metric": "larger_households.sd_total_days", + "absolute_gap": 0.010530264051684455, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.sd_total_weekly_hours", + "absolute_gap": 0.07370280213290151, + "relative": true, + "limit": 0.2, + "passed": true + }, + { + "metric": "larger_households.all_children_attend", + "absolute_gap": 0.0128154922623471, + "relative": false, + "limit": 0.05, + "passed": true + } + ], + "interpretation": "provisional diagnostic screens; not publication authorization" + } + }, + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "matching_levels": { + "age": 46, + "age,parent_work_status": 331, + "age,parent_work_status,income_band": 1287, + "age,region,parent_work_status,income_band": 1382 + }, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "candidate_frame_written": true, + "environment": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "microcosm-build": "0.1.0", + "microcosm-frame": "0.1.0", + "policyengine-us": "2.2.1", + "python": "3.13.5" + }, + "code_sha256": { + "prepare_us_childcare_attendance.py": "19eabbe3b972bf751aa64892fb8a4f06d7d40bf05a742c9546d5b1b3e54fd2fb", + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "nsece_childcare.py": "e11eef389f47433a2d407aaf7c01214dbefccb702b3fefe8fa6502c7e947ab4d", + "nsece_childcare_bridge.py": "517b3c3ae02e2316b028fd15bc60a090e7962d716d46b80e3e73067f08d5ff29", + "nsece_childcare_sibling_validation.py": "abd2a4c88d3c4585e055fae93be3ac00c060405fc6e24bb079b67196aa88e422", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "c5243b7f23865c352acee8f5946bb8c7b578645c7b5e56c24632fa94dd9dbecb", + "childcare_attendance_receipt.py": "d93ecfe1725bef2c0b87a137eaab88856fa9a76e601227089ceb94daa4335865", + "nsece_childcare_qrf.py": "f8ae9695a648bd163e04ae2ab92a9cd08ce9c29e7e19ed293c118934ff6ef0b1", + "childcare_sensitivity.py": "17a684c51ed48f164006d485aa2177d8c1f03609290e96eae377c625bb6d93cf", + "nsece_childcare_assessment.py": "537d5d839ce9df4f1772ee887597811990aab5b7f10a863b70fe6630583fa3b7", + "nsece_childcare_pooling.py": "a779a0db7fddbb9e33aa7f8c8f7752b5f4a083309bb7d8f03d6ab72555a439f2", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "childcare.py": "a8502e1fc15457a7f120936a8940fbb51cba3e2299d92ca9cd69b749f6deea2f", + "childcare_attendance_source.json": "c5beeeb09ab330c305e3250bc7db5a5003336d26fa508872211cb18958ab9e97", + "education_assistance_source.py": "e259e3fce64fdcab62953f79a1aa41719f81c64c7c5dc1a63f8b1e493ff5f83a", + "geography_constants.py": "96938b5dd6bba8f10d0f52d10a9cdcbc16ae9506c4424f58e10347998ff02a8a", + "uv.lock": "eb328a5cf6cc0bc567c209fce03294f4f3b0b5e59adf47dfa7ff4173545e03be" + }, + "candidate_checkpoint_sha256": "5cb9aa418768b5a82ee351e8b737f58c8989212f1ffbee36d605c22887e989a5", + "parent_population_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "production_stage_executed": true, + "candidate_receipts": { + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "matching_levels": { + "age": 46, + "age,parent_work_status": 331, + "age,parent_work_status,income_band": 1287, + "age,region,parent_work_status,income_band": 1382 + }, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operations": [ + { + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance", + "age_min": 0, + "age_max": 12 + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "operation": "regular_hours_schedule_bridge", + "nearest_donors": 10 + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + }, + "childcare_attendance_binding": { + "schema_version": 1, + "binding_sha256": "5a67c4cdbefa261a6ba7be056ef37afbf6a20755c649432c10020026a7f1e9a2", + "execution_sha256": "a9c0c33d4eac2a876e7cee52c65b8e2ad3d2200bed62d91bc0465c05f590cc27", + "source_people": 166321, + "retained_people": 166321, + "execution": { + "recipe": { + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + }, + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "e11eef389f47433a2d407aaf7c01214dbefccb702b3fefe8fa6502c7e947ab4d", + "nsece_childcare_bridge.py": "517b3c3ae02e2316b028fd15bc60a090e7962d716d46b80e3e73067f08d5ff29", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09", + "childcare_attendance_stage.py": "c5243b7f23865c352acee8f5946bb8c7b578645c7b5e56c24632fa94dd9dbecb", + "childcare_attendance_receipt.py": "d93ecfe1725bef2c0b87a137eaab88856fa9a76e601227089ceb94daa4335865" + } + }, + "context": { + "nsece_childcare_attendance": { + "study": "ICPSR39466.v1", + "source_year": 2024, + "measurement": "union_of_ECE_calendar_blocks_excluding_K8", + "monthly_conversion": "floor(days_per_week * 52 / 12 + 0.5)", + "national_representativeness_validated": false, + "calendar_bounds": { + "source": "NSECE 2024 Household User Guide HH-334, HH-565--568", + "meaning": "definite and possible ECE blocks; not imputations or confidence intervals", + "partial_calendar_zero": "unresolved assumed parental care, not observed nonattendance", + "missing_calendar": "uninformative 0--168 hours and 0--7 days" + }, + "artifacts": [ + { + "sha256": "b42c69874de627273cdf698e4b8cdd39d3d4fdfef7d1295c239772cbc4e6c5a2", + "size_bytes": 122764947 + }, + { + "sha256": "c2e04a7a3eb6e187dc6d4496be1de77508354e05c6249725ae072f8a5f2ce18e", + "size_bytes": 269542239 + } + ], + "noncalendar_bridge": { + "seed": 915, + "completed_children": 3046, + "unsupported_children": 0, + "matching_levels": { + "age": 46, + "age,parent_work_status": 331, + "age,parent_work_status,income_band": 1287, + "age,region,parent_work_status,income_band": 1382 + }, + "observed": "regular weekly hours from May/fall questionnaire", + "imputed": "attended days and irregular care jointly from nearest regular-hour calendar donors", + "assumption": "conditional calendar pattern and irregular care transfer across questionnaire instruments" + }, + "seed": 915, + "match_columns": [ + "age", + "region", + "parent_work_status", + "income_band" + ], + "fallback_match_columns": [ + [ + "age", + "parent_work_status", + "income_band" + ], + [ + "age", + "parent_work_status" + ], + [ + "age" + ] + ], + "sibling_dependence": 0.7413149380294699, + "candidate_only": true + }, + "childcare_predictor_harmonization": { + "source": "ASEC resident parent line pointers", + "parent_universe": "parents of any child age 0 through 12 in household", + "work_measure": "hours_worked_last_week > 0", + "geography": "Census region from target state_fips", + "income_source_receipts": [ + { + "income_year": 2022, + "sha256": "19b56537e50e7663f954361ef2bb5ce9cef8d9d45f156fe1a69a99b654198ffe", + "matched_people": 54464, + "source_rows": 146133 + }, + { + "income_year": 2023, + "sha256": "21a2b9e0e4b08534563578a45acad77868af4ae9a7d46f23776b707d4a559aa7", + "matched_people": 54654, + "source_rows": 144265 + }, + { + "income_year": 2024, + "sha256": "06921fe83fc66c907e6c7b86b82255dc70458ee7d76258fc48297cb34f0c06b5", + "matched_people": 57203, + "source_rows": 142125 + } + ], + "income": "Household sum of measured ASEC PTOTVAL, CPI-U adjusted from source year to 2023 dollars" + }, + "childcare_outside_domain_baseline": { + "engine_version": "2.2.1", + "values": { + "childcare_attending_days_per_month": 0.0, + "childcare_days_per_week": 0.0, + "childcare_hours_per_day": 0.0 + }, + "inherited_counts": { + "childcare_attending_days_per_month": 134432, + "childcare_days_per_week": 134432, + "childcare_hours_per_day": 134432 + }, + "interpretation": "baseline retained; not evidence of nonattendance outside ages 0-12" + }, + "childcare_attendance_stage": { + "stage": "nsece_childcare_attendance", + "outputs": [ + "childcare_attending_days_per_month", + "childcare_days_per_week", + "childcare_hours_per_day" + ], + "seed": 915, + "operations": [ + { + "kind": "derive_childcare_inputs", + "operation": "calendar_attendance", + "age_min": 0, + "age_max": 12 + }, + { + "kind": "fit", + "operation": "fit_sibling_dependence", + "rho": 0.7413149380294699 + }, + { + "kind": "derive_childcare_inputs", + "operation": "regular_hours_schedule_bridge", + "nearest_donors": 10 + }, + { + "kind": "derive", + "operation": "harmonize_asec_childcare_predictors" + }, + { + "kind": "derive_childcare_inputs", + "operation": "joint_weighted_schedule_transfer", + "preserve_observed": true + }, + { + "kind": "export_policy", + "operation": "inherit_engine_baseline" + } + ], + "sibling_dependence": { + "rho": 0.7413149380294699, + "unconstrained_rho": 0.7413149380294699, + "households": 1941, + "observed_both_in_care": 0.32858869865910467, + "independent_both_in_care": 0.2585427028841666, + "shared_both_in_care": 0.3530315526045447, + "estimation": "youngest pair in fully observed households; household design weights" + }, + "modeled_age_domain": [ + 0, + 12 + ], + "outside_domain_policy": "inherit_engine_baseline" + } + } + } + } + }, + "native_candidate_written": true, + "native_candidate_sha256": "26e8811d2f6822ae555280fea06d1231d868c12764a20ab8e66086fec017f4db" +} diff --git a/experiments/us-childcare-attendance/review-fixes-verification.json b/experiments/us-childcare-attendance/review-fixes-verification.json new file mode 100644 index 000000000..b4d6978da --- /dev/null +++ b/experiments/us-childcare-attendance/review-fixes-verification.json @@ -0,0 +1,39 @@ +{ + "parent_sha256": "48b9d479fb4fd1c3537f9383ce4697d130b6f618658409d74f6233c43b994c7e", + "checkpoint_sha256": "5cb9aa418768b5a82ee351e8b737f58c8989212f1ffbee36d605c22887e989a5", + "native_sha256": "26e8811d2f6822ae555280fea06d1231d868c12764a20ab8e66086fec017f4db", + "source_stage_report_sha256": "eb1f20876a4b5d41d045b1fb65cc5a0029c0fad3b800e26e7d6e83833c6be743", + "verification_code_sha256": "3b4cd482f6465325f66d1d8e1b3d680b281073d7dcb6b4bbedc7899e16d27ed4", + "attendance_recipe": { + "code_sha256": { + "childcare_attendance.py": "da00ef1fb45232a731bd4a3b33d93cd0aeac0682b28d50e4d53cb79db12ce96e", + "childcare_attendance_receipt.py": "d93ecfe1725bef2c0b87a137eaab88856fa9a76e601227089ceb94daa4335865", + "childcare_attendance_stage.py": "c5243b7f23865c352acee8f5946bb8c7b578645c7b5e56c24632fa94dd9dbecb", + "childcare_population.py": "19fef5e4cc1838734783c2eb911f556596256cf093fb1063a403a6eadf49d04e", + "nsece_childcare.py": "e11eef389f47433a2d407aaf7c01214dbefccb702b3fefe8fa6502c7e947ab4d", + "nsece_childcare_bridge.py": "517b3c3ae02e2316b028fd15bc60a090e7962d716d46b80e3e73067f08d5ff29", + "nsece_childcare_dependence.py": "1c5123a77f84460ba517d2b4c6c3e04ae658bf36e9cf54ed69ddbcacc85efc09" + }, + "contract_sha256": "ae648c6cb0e2838fdf142975e9c536eb0683d9630fd1738096d524222ffb01dc", + "runtime_versions": { + "numpy": "2.4.6", + "pandas": "3.0.3", + "policyengine-us": "2.2.1" + } + }, + "content_binding_sha256": "5a67c4cdbefa261a6ba7be056ef37afbf6a20755c649432c10020026a7f1e9a2", + "engine_version": "2.2.1", + "core_version": "3.32.5", + "people": 166321, + "households": 57240, + "under13_children": 31889, + "verified_native_loaders": [ + "microcosm.build.us_runtime.h5_io.load_legacy_calibrated_us_h5", + "microcosm.build.us_runtime.l0_refit_export.load_us_frame" + ], + "all_original_columns_and_weights_preserved": true, + "all_attendance_values_preserved_on_native_reload": true, + "comparison": "exact values and dtypes, normalizing only pandas string storage backend", + "interpretation": "artifact integrity and preservation only; no statistical certification or publication", + "production_ready": false +} From 6863cefac3ef115f3210e8059145127342e67e68 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Thu, 17 Sep 2026 16:27:18 -0400 Subject: [PATCH 13/16] Register the receipt-only HDF write and correct the new attendance tests Classify write_native_childcare_receipt as a non-table HDF write, let the ACA source-output test's Frame stub carry metadata and assert it survives, and fix two new tests that indexed the wrong source row and used a frame whose adult already had observed attendance. Co-Authored-By: Claude Fable 5.1 --- .../src/microcosm/build/frame_serializer_registry.py | 9 +++++++++ .../tests/test_us_fiscal_refresh_builder.py | 9 +++++++-- .../microcosm-build/tests/test_us_nsece_childcare.py | 11 +++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py b/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py index 4f007e9c8..1fd3c3bb6 100644 --- a/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py +++ b/packages/microcosm-build/src/microcosm/build/frame_serializer_registry.py @@ -179,6 +179,15 @@ class HdfWriteExclusion: ), reason="Copies Microcosm-owned root attributes only.", ), + HdfWriteExclusion( + exclusion_id="nsece_childcare_native_receipt", + writer=HdfWriteSite( + "packages/microcosm-build/src/microcosm/build/us_runtime/" + "childcare_attendance_receipt.py", + "write_native_childcare_receipt", + ), + reason="Adds one JSON receipt key; never writes an entity table.", + ), HdfWriteExclusion( exclusion_id="acs_transfer_raw_draw_bank", writer=HdfWriteSite( diff --git a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py index f4d0d7f76..5164d54e7 100644 --- a/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py +++ b/packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py @@ -7530,6 +7530,7 @@ class FakeFrame: schema = object() weighted_entities = () strata = None + metadata = {"upstream_receipt": "preserved"} def table(self, entity): assert entity == "tax_unit" @@ -7566,7 +7567,9 @@ def fake_run_source_stage( monkeypatch.setattr( builder, "Frame", - lambda tables, schema, weights, strata: SimpleNamespace(tables=tables), + lambda tables, schema, weights, strata, *, metadata=None: SimpleNamespace( + tables=tables, metadata=metadata + ), ) specs = ( @@ -7598,13 +7601,15 @@ def fake_run_source_stage( ), ) - builder._with_aca_marketplace_source_outputs( + result = builder._with_aca_marketplace_source_outputs( FakeFrame(), specs, seed=42, simulation=object(), ) + # Upstream receipts (e.g. the attendance binding) must survive this stage. + assert result.metadata == {"upstream_receipt": "preserved"} assert captured["stage"] == builder.US_ACA_MARKETPLACE_STAGE assert captured["stop_after"] is None target_tables = captured["tables"] diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index f7346787d..29cbcaf0b 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -772,7 +772,11 @@ def test_bridge_widens_a_thin_cell_instead_of_keeping_distant_donors(): result = bridge_nsece_noncalendar_attendance( derive_nsece_childcare(hh, cal), seed=915 ) - child = result.children.iloc[11] + bridged = result.children.loc[ + result.children.attendance_status.eq("summary_bridge") + ] + assert len(bridged) == 1 + child = bridged.iloc[0] assert child.schedule_bridge_match == "age,parent_work_status,income_band" assert child[DAYS] == 5 assert child[HOURS] == 8 @@ -951,9 +955,12 @@ def test_production_stage_refuses_unbound_existing_values(monkeypatch): def test_default_outside_domain_policy_fails_early_and_names_the_flag(monkeypatch): monkeypatch.setattr(stage, "load_nsece_childcare", lambda *args: _source()) + frame = _asec_frame() + # The adult has no observed attendance, as on every production parent. + frame.table("person").loc[0, list(US_CHILDCARE_ATTENDANCE_COLUMNS)] = np.nan with pytest.raises(ValueError, match="inherit-outside-domain-baseline"): stage.with_us_childcare_attendance_inputs( - _asec_frame(), + frame, household_tsv="fake", calendar_tsv="fake", asec_source_cache=None, From d2601c7a0749b134822086f4783e03501c750e32 Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Thu, 17 Sep 2026 17:11:32 -0400 Subject: [PATCH 14/16] Preserve childcare receipts through repairs and wire exact-k source inputs --- CLAUDE.md | 5 +- README.md | 4 +- .../915-us-childcare-attendance.added.md | 2 +- docs/us-childcare-attendance.md | 29 +++- experiments/us-childcare-attendance/README.md | 28 ++-- .../tests/test_us_exact_k_ladder_launcher.py | 127 ++++++++++++++++++ .../tests/test_us_nsece_childcare.py | 53 +++++++- tools/build_us_exact_k_ladder_release.py | 99 ++++++++++++++ tools/build_us_fiscal_refresh_release.py | 2 + 9 files changed, 331 insertions(+), 18 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index aa9e5b18d..ec3ab0eb8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,8 +106,9 @@ US fiscal builds require the NSECE childcare-attendance stage (`--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, `--childcare-attendance-inherit-outside-domain-baseline`): attendance is a required, non-waivable export input bound by a per-person receipt, a build -without it is refused before calibration, and -`tools/build_us_exact_k_ladder_release.py` cannot yet pass these flags. See +without it is refused before calibration. The exact-k launcher accepts the same +inputs through an optional `childcare_attendance` configuration object, checks +the packaged survey pins, and forwards them to the fiscal builder. See [the attendance runbook](docs/us-childcare-attendance.md). The US native-SPM-role source-enrichment lane is a separate release type: diff --git a/README.md b/README.md index 05ef35b01..4edfd24d4 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,9 @@ publish CLI posts a release alert to Slack — `#populace-us` or `#populace-uk`, chosen from the repo id. US exact-k ladder candidates use a tag-only lane. Run -`tools/build_us_exact_k_ladder_release.py`, then execute the `publish_command` +`tools/build_us_exact_k_ladder_release.py` with the +[`childcare_attendance` source configuration](docs/us-childcare-attendance.md#build-and-reproduction) +unless the pool already carries bound attendance inputs, then execute the `publish_command` recorded in `package_result.json`. That command includes `--create-tag`, `--no-latest`, and `--tag-only`: it uploads the immutable release and creates its tag without committing candidate artifacts or release copies to the production diff --git a/changelog.d/915-us-childcare-attendance.added.md b/changelog.d/915-us-childcare-attendance.added.md index 595f880fe..655b2a110 100644 --- a/changelog.d/915-us-childcare-attendance.added.md +++ b/changelog.d/915-us-childcare-attendance.added.md @@ -1 +1 @@ -Derive the three PolicyEngine-US childcare attendance inputs (`childcare_attending_days_per_month`, `childcare_days_per_week`, `childcare_hours_per_day`) for children ages 0–12 from the licensed 2024 NSECE household and calendar files and transfer them onto the US fiscal refresh population (#915). `tools/build_us_fiscal_refresh_release.py` gains `--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, `--childcare-attendance-asec-cache` and `--childcare-attendance-inherit-outside-domain-baseline`; the three inputs become required release inputs bound to a per-person receipt that the native loaders and the builder's `--base-h5` loader check, that the L0 refit export carries forward, and that no coverage override can waive. A build with neither the source files nor bound attendance is refused before calibration. PolicyEngine-US defaults are unchanged and no population is published. +Derive the three PolicyEngine-US childcare attendance inputs (`childcare_attending_days_per_month`, `childcare_days_per_week`, `childcare_hours_per_day`) for children ages 0–12 from the licensed 2024 NSECE household and calendar files and transfer them onto the US fiscal refresh population (#915). `tools/build_us_fiscal_refresh_release.py` gains `--childcare-attendance-household-tsv`, `--childcare-attendance-calendar-tsv`, `--childcare-attendance-asec-cache` and `--childcare-attendance-inherit-outside-domain-baseline`; the three inputs become required release inputs bound to a per-person receipt that the native loaders and the builder's `--base-h5` loader check, that the L0 refit export carries forward, and that no coverage override can waive. The builder preserves receipts through value repairs, and the exact-k launcher accepts a `childcare_attendance` source configuration with packaged survey-pin checks. A build with neither the source files nor bound attendance is refused before calibration. PolicyEngine-US defaults are unchanged and no population is published. diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index c5ba785d6..dd361132e 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -200,7 +200,10 @@ Code hashes and environment versions accompany the aggregate preparation report. The receipt binds the source hashes, contract, recipe code, runtime versions, seed, matching/bridge settings, fitted dependence, and outside-domain policy to each person's ID, household link, age, and three attendance values. Both native -US loaders and the fiscal builder's `--base-h5` loader restore and check it. A +US loaders and the fiscal builder's `--base-h5` loader restore and check it. The +builder's Social Security and capital-gains repairs preserve that metadata even +when no rescaling is needed, so a prepared candidate can reach the attendance +reuse check without losing its receipt. A missing or altered receipt fails at load. Recipe code and runtime versions are compared when a stage binds and when the fiscal build exports, not at read-only load, so a released file stays readable as a reference after a dependency bump. @@ -235,7 +238,29 @@ Existing release input gates still apply; a build without required attendance inputs cannot substitute an engine default for a persisted input. A fiscal build given neither the TSV flags nor bound attendance is refused before calibration, and the -exact-k ladder wrapper cannot yet pass these flags. +exact-k ladder wrapper forwards these inputs from an optional top-level +`childcare_attendance` object in its existing schema-v1 configuration: + +```json +{ + "childcare_attendance": { + "household_tsv": "/local/39466-0005-Data.tsv", + "calendar_tsv": "/local/39466-0004-Data.tsv", + "asec_source_cache": "/local/asec", + "inherit_outside_domain_baseline": true + } +} +``` + +Add this object alongside `pool`, `ladder`, `targets`, `calibration`, and +`release`; it is not a complete launcher configuration. Relative paths resolve +against the config file. Both TSVs and an explicit boolean outside-domain policy +are required when the object is present; the ASEC cache is optional. The launcher +checks the TSVs against the packaged source hashes before invoking the builder, +and the source stage checks them again when loading. Omit the object only when +the input already carries attendance accepted by the production receipt checks. +This wiring does not qualify a new survey origin or replace the full fiscal +build and population validation. Unknown values outside ages 0–12 stay null in the source model. The explicit outside-domain export policy fills only these missing cells with the pinned diff --git a/experiments/us-childcare-attendance/README.md b/experiments/us-childcare-attendance/README.md index fa3dd8196..d61350cf5 100644 --- a/experiments/us-childcare-attendance/README.md +++ b/experiments/us-childcare-attendance/README.md @@ -30,10 +30,14 @@ model is adopted, no population is published, and every report retains values. The User's Guide (HH-483) defines −1 as "No parents" and the measure as work attended in the week before the interview, the same concept as the ASEC side; income (HH-175) has a minimum of 0. -- **Not changed.** The exact-k ladder wrapper cannot yet pass the NSECE flags. - Two early builder repair steps still drop frame metadata before the attendance - stage, so a receipted candidate reused as `--base-h5` is refused early instead - of being reused. +- **Fiscal integration follow-up.** The exact-k wrapper accepts a + `childcare_attendance` configuration object, validates its TSVs against the + packaged source pins, and forwards the source paths and outside-domain policy. + The early Social Security and capital-gains repairs now preserve frame + metadata, allowing a receipted `--base-h5` to reach the reuse check. Synthetic + coverage exercises native ingress, both changed and unchanged repairs, receipt + reuse, engine export and native reload. These changes do not alter the source + recipe or the candidate and report hashes below. ## Bridge widening @@ -119,13 +123,15 @@ update. ## Code checks for this update Repository-wide ruff lint, changed-file formatting and the tracked CI test -inventory pass. New synthetic tests cover thin-cell widening, reserve codes, the -early outside-domain failure, the pre-calibration refusal and content-only -loading; two existing tests changed with the receipt payload and the recipe -check. **The test suite was not run locally for this update**; GitHub CI is the -first run. The production stage, native export, both native loaders and the -three validation tools ran end to end on the licensed local inputs. The fiscal -builder itself has still not been run end to end with attendance. +inventory pass. The fiscal integration follow-up passes 37 targeted local tests, +including launcher argument forwarding and source-pin refusal, native ingress, +both changed and unchanged value repairs, attendance reuse, and native export +and reload. The earlier four CI failures were fixed in `6863cefa`. + +The population rebuild used the production stage, native export, both native +loaders and the three validation tools end to end on the licensed local inputs. +The fiscal builder itself has still not been run end to end with attendance; +the synthetic integration checks do not establish production readiness. ## Interval training and paired sensitivity — 2026-09-16 diff --git a/packages/microcosm-build/tests/test_us_exact_k_ladder_launcher.py b/packages/microcosm-build/tests/test_us_exact_k_ladder_launcher.py index 5f600b70c..34fe24392 100644 --- a/packages/microcosm-build/tests/test_us_exact_k_ladder_launcher.py +++ b/packages/microcosm-build/tests/test_us_exact_k_ladder_launcher.py @@ -222,8 +222,10 @@ def test_incumbent_and_target_surface_pins_fail_closed( ) +@pytest.mark.parametrize("inherit_baseline", [None, False, True]) def test_launcher_arguments_are_accepted_by_the_house_builder_parser( tmp_path: Path, + inherit_baseline: bool | None, ) -> None: launcher = _launcher_module() payload = _config_payload( @@ -232,6 +234,13 @@ def test_launcher_arguments_are_accepted_by_the_house_builder_parser( ) payload["targets"]["ssi_take_up_prior_weight_basis"] = "ssi.json" payload["targets"]["ssi_take_up_prior_weight_basis_sha256"] = "1" * 64 + if inherit_baseline is not None: + payload["childcare_attendance"] = { + "household_tsv": "household.tsv", + "calendar_tsv": str(tmp_path / "calendar.tsv"), + "asec_source_cache": "asec", + "inherit_outside_domain_baseline": inherit_baseline, + } config = launcher._read_config(_write_config(tmp_path, payload)) argv = launcher._builder_argv( @@ -249,6 +258,124 @@ def test_launcher_arguments_are_accepted_by_the_house_builder_parser( assert parsed.no_staging is True assert parsed.ssi_take_up_prior_weight_basis == tmp_path / "ssi.json" assert parsed.ssi_take_up_prior_weight_basis_sha256 == "1" * 64 + if inherit_baseline is None: + assert parsed.childcare_attendance_household_tsv is None + assert parsed.childcare_attendance_calendar_tsv is None + assert parsed.childcare_attendance_asec_cache is None + assert parsed.childcare_attendance_inherit_outside_domain_baseline is False + else: + assert parsed.childcare_attendance_household_tsv == tmp_path / "household.tsv" + assert parsed.childcare_attendance_calendar_tsv == tmp_path / "calendar.tsv" + assert parsed.childcare_attendance_asec_cache == tmp_path / "asec" + assert ( + parsed.childcare_attendance_inherit_outside_domain_baseline + is inherit_baseline + ) + + +@pytest.mark.parametrize( + "source", + [ + None, + {}, + {"household_tsv": "household.tsv"}, + {"calendar_tsv": "calendar.tsv"}, + { + "household_tsv": "h", + "calendar_tsv": "c", + "inherit_outside_domain_baseline": "false", + }, + { + "household_tsv": "h", + "calendar_tsv": "c", + "inherit_outside_domain_baseline": 1, + }, + { + "household_tsv": "h", + "calendar_tsv": "c", + "inherit_outside_domain_baseline": True, + "asec_source_cache": "", + }, + { + "household_tsv": "h", + "calendar_tsv": "c", + "inherit_outside_domain_baseline": True, + "unknown": True, + }, + ], +) +def test_attendance_config_rejects_incomplete_or_ambiguous_inputs(tmp_path, source): + payload = _config_payload() + payload["childcare_attendance"] = source + with pytest.raises(ValueError, match="childcare_attendance"): + _launcher_module()._read_config(_write_config(tmp_path, payload)) + + +@pytest.mark.parametrize("bad_source", [None, "household", "calendar", "cache"]) +def test_attendance_source_pins_are_checked_before_the_builder( + tmp_path, monkeypatch, bad_source +): + from microcosm.build.us_runtime import childcare_attendance + + launcher = _launcher_module() + (tmp_path / "ledger").mkdir() + incumbent = tmp_path / "incumbent.json" + incumbent.write_text(json.dumps({"target_surface": {"sha256": "e" * 64}})) + household = tmp_path / "household.tsv" + calendar = tmp_path / "calendar.tsv" + household.write_text("synthetic household fixture") + calendar.write_text("synthetic calendar fixture") + # Licensed source bytes stay out of CI; the real contract is pinned in + # test_source_stage_contract_pins_outputs_and_assets. + pins = {"DS5": _sha256(household), "DS4": _sha256(calendar)} + monkeypatch.setattr( + childcare_attendance, + "childcare_attendance_contract", + lambda: {"artifacts": [{"dataset": k, "sha256": v} for k, v in pins.items()]}, + ) + payload = _config_payload() + payload["targets"]["incumbent_diagnostics_sha256"] = _sha256(incumbent) + payload["childcare_attendance"] = { + "household_tsv": "household.tsv", + "calendar_tsv": "calendar.tsv", + "inherit_outside_domain_baseline": True, + } + if bad_source in ("household", "calendar"): + (tmp_path / f"{bad_source}.tsv").write_text("changed after pinning") + if bad_source == "cache": + payload["childcare_attendance"]["asec_source_cache"] = "missing-cache" + monkeypatch.setattr( + launcher, + "load_simulation_ready_us_multispine_pool_manifest", + lambda *a, **kw: { + "publication_run_id": "fixture-publication", + "agreement_gate": {"passed": True}, + "provenance_counts": {"household": {"rows": 8}}, + }, + ) + calls = [] + + def builder(argv): + parsed = launcher.fiscal_release._parse_args(argv) + assert parsed.childcare_attendance_household_tsv == household + assert parsed.childcare_attendance_calendar_tsv == calendar + assert parsed.childcare_attendance_asec_cache is None + assert parsed.childcare_attendance_inherit_outside_domain_baseline + calls.append(argv) + + kwargs = dict( + pool_manifest=tmp_path / "pool.manifest.json", + config_path=_write_config(tmp_path, payload), + out=tmp_path / "out", + release_builder=builder, + ) + if bad_source is None: + launcher.launch(**kwargs) + assert len(calls) == 1 + else: + with pytest.raises(ValueError, match="SHA-256 mismatch|must be a directory"): + launcher.launch(**kwargs) + assert calls == [] def test_launcher_delegates_to_house_builder_and_never_publishes( diff --git a/packages/microcosm-build/tests/test_us_nsece_childcare.py b/packages/microcosm-build/tests/test_us_nsece_childcare.py index 29cbcaf0b..beda647f1 100644 --- a/packages/microcosm-build/tests/test_us_nsece_childcare.py +++ b/packages/microcosm-build/tests/test_us_nsece_childcare.py @@ -656,11 +656,32 @@ def test_asec_income_sidecar_checks_identity_and_preserves_raw_missingness( @pytest.mark.requires_us -def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch, tmp_path): +@pytest.mark.parametrize("repair_factor", [1.0, 2.0]) +def test_registered_attendance_recipe_uses_real_transform_chain( + monkeypatch, tmp_path, repair_factor +): + import importlib.util + from pathlib import Path + from microcosm.build.us_runtime import childcare_attendance_stage as stage + builder_path = ( + Path(__file__).resolve().parents[3] + / "tools" + / "build_us_fiscal_refresh_release.py" + ) + builder_spec = importlib.util.spec_from_file_location( + "attendance_fiscal_builder", builder_path + ) + builder = importlib.util.module_from_spec(builder_spec) + builder_spec.loader.exec_module(builder) frame = _asec_frame() frame.table("household")["household_source_id"] = "family" + for column in ( + *builder.US_SOCIAL_SECURITY_COMPONENT_TARGET_ROLES.values(), + "non_sch_d_capital_gains", + ): + frame.table("person")[column] = [1.0, 0.0] hh, cal = _raw() hh["HHC4_AGE_AT_USAGE_1"] = frame.table("person").loc[1, "age"] * 12 hh["HH4_REGION"] = 1 @@ -724,6 +745,36 @@ def test_registered_attendance_recipe_uses_real_transform_chain(monkeypatch, tmp assert "rows" not in evidence assert_bound_childcare_attendance(load_us_frame(path)) + # Reusing a prepared native parent traverses these two repairs before the + # attendance stage. Both changed and unchanged repairs must retain lineage. + reused = builder._load_frame(path) + before = reused.table("person")[list(US_CHILDCARE_ATTENDANCE_COLUMNS)].copy() + ssa_targets = [ + SimpleNamespace(metadata={"target_role": role}, value=100.0 * repair_factor) + for role in builder.US_SOCIAL_SECURITY_COMPONENT_TARGET_ROLES + ] + cgd_target = SimpleNamespace( + name="irs_soi.ty2023.table_1_4.all.capital_gain_distributions_amount", + metadata={"aged_to": "2024"}, + value=100.0 * repair_factor, + ) + for repair, targets in ( + (builder._with_social_security_component_value_repair, ssa_targets), + (builder._with_non_sch_d_cgd_value_repair, [cgd_target]), + ): + reused, repair_receipt = repair(reused, targets) + assert repair_receipt["applied"] == (repair_factor != 1.0) + builder._require_bound_childcare_attendance(reused) + assert stage.with_us_childcare_attendance_inputs(reused, **options) is reused + repaired_path = tmp_path / "repaired.h5" + builder.PolicyEngineUSEngine().write_dataset(reused, repaired_path, period=2026) + stage.persist_native_childcare_receipt(repaired_path, reused) + reloaded = load_us_frame(repaired_path) + assert_bound_childcare_attendance(reloaded) + assert_frame_equal( + reloaded.table("person")[list(US_CHILDCARE_ATTENDANCE_COLUMNS)], before + ) + def test_bridge_retains_all_equally_near_donors(): from microcosm.build.us_runtime.nsece_childcare_bridge import ( diff --git a/tools/build_us_exact_k_ladder_release.py b/tools/build_us_exact_k_ladder_release.py index 17668c5e6..9ec20d607 100644 --- a/tools/build_us_exact_k_ladder_release.py +++ b/tools/build_us_exact_k_ladder_release.py @@ -40,6 +40,12 @@ ``ssi_take_up_prior_weight_basis`` and ``ssi_take_up_prior_weight_basis_sha256`` fields for the house one-retry delivery-gate protocol. + +The optional ``childcare_attendance`` object supplies ``household_tsv`` and +``calendar_tsv`` paths, an explicit ``inherit_outside_domain_baseline`` boolean, +and an optional ``asec_source_cache`` directory. Paths are relative to the config +file; survey hashes come from the packaged NSECE source contract. Without this +object, the pool must already carry valid, bound attendance inputs. """ # The sibling house builder is importable only after its tools directory is @@ -76,6 +82,16 @@ _RELEASE_ID = re.compile(r"[A-Za-z0-9-]+") +@dataclass(frozen=True) +class ChildcareAttendanceConfig: + """Local survey inputs; the source contract owns their immutable pins.""" + + household_tsv: Path + calendar_tsv: Path + inherit_outside_domain_baseline: bool + asec_source_cache: Path | None + + @dataclass(frozen=True) class LadderReleaseConfig: """Validated, path-resolved launcher configuration.""" @@ -101,6 +117,7 @@ class LadderReleaseConfig: refit_l2_lambda: float release_id: str repo_id: str + childcare_attendance: ChildcareAttendanceConfig | None = None def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: @@ -137,6 +154,7 @@ def _read_config(path: Path) -> LadderReleaseConfig: "calibration", "release", }, + optional={"childcare_attendance"}, label=f"ladder config {path}", ) if ( @@ -298,6 +316,45 @@ def _read_config(path: Path) -> LadderReleaseConfig: refit_l2_lambda=refit_l2_lambda, release_id=release_id, repo_id=repo_id, + childcare_attendance=( + _read_childcare_attendance(root["childcare_attendance"], config_dir) + if "childcare_attendance" in root + else None + ), + ) + + +def _read_childcare_attendance( + value: object, config_dir: Path +) -> ChildcareAttendanceConfig: + source = _object(value, label="childcare_attendance") + _keys( + source, + required={ + "household_tsv", + "calendar_tsv", + "inherit_outside_domain_baseline", + }, + optional={"asec_source_cache"}, + label="childcare_attendance", + ) + inherit = source["inherit_outside_domain_baseline"] + if not isinstance(inherit, bool): + raise ValueError( + "childcare_attendance.inherit_outside_domain_baseline must be a boolean." + ) + + def source_path(key: str) -> Path: + raw = _nonempty_string(source[key], label=f"childcare_attendance.{key}") + return (config_dir / raw).resolve() + + return ChildcareAttendanceConfig( + household_tsv=source_path("household_tsv"), + calendar_tsv=source_path("calendar_tsv"), + inherit_outside_domain_baseline=inherit, + asec_source_cache=( + source_path("asec_source_cache") if "asec_source_cache" in source else None + ), ) @@ -390,6 +447,32 @@ def _validate_pins_and_resolve_k( f"{observed_prior_basis_sha256}, expected " f"{config.ssi_take_up_prior_weight_basis_sha256}." ) + if config.childcare_attendance is not None: + from microcosm.build.us_runtime.childcare_attendance import ( + childcare_attendance_contract, + ) + + source = config.childcare_attendance + pins = { + artifact["dataset"]: artifact["sha256"] + for artifact in childcare_attendance_contract()["artifacts"] + } + for dataset, path in ( + ("DS5", source.household_tsv), + ("DS4", source.calendar_tsv), + ): + if _sha256(path) != pins[dataset]: + raise ValueError( + f"Childcare attendance {dataset} SHA-256 mismatch: {path}. " + "Use the TSV from the pinned NSECE release." + ) + if ( + source.asec_source_cache is not None + and not source.asec_source_cache.is_dir() + ): + raise ValueError( + "childcare_attendance.asec_source_cache must be a directory." + ) return k, pool_manifest @@ -452,6 +535,22 @@ def _builder_argv( str(config.ssi_take_up_prior_weight_basis_sha256), ] ) + if config.childcare_attendance is not None: + source = config.childcare_attendance + argv.extend( + [ + "--childcare-attendance-household-tsv", + str(source.household_tsv), + "--childcare-attendance-calendar-tsv", + str(source.calendar_tsv), + ] + ) + if source.asec_source_cache is not None: + argv.extend( + ["--childcare-attendance-asec-cache", str(source.asec_source_cache)] + ) + if source.inherit_outside_domain_baseline: + argv.append("--childcare-attendance-inherit-outside-domain-baseline") return argv diff --git a/tools/build_us_fiscal_refresh_release.py b/tools/build_us_fiscal_refresh_release.py index b7c0e37fb..876e870ef 100644 --- a/tools/build_us_fiscal_refresh_release.py +++ b/tools/build_us_fiscal_refresh_release.py @@ -5815,6 +5815,7 @@ def _with_social_security_component_value_repair( {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, frame.strata, mass_log=frame.mass_log, + metadata=frame.metadata, ) return repaired, { "method": "rescale_social_security_component_leaves_to_ssa_targets", @@ -5900,6 +5901,7 @@ def _with_non_sch_d_cgd_value_repair( {entity: frame.weights_for(entity) for entity in frame.weighted_entities}, frame.strata, mass_log=frame.mass_log, + metadata=frame.metadata, ) return repaired, { "method": "rescale_non_sch_d_capital_gains_to_soi_table_1_4_fact", From 4f746222fb14723b4f897a5146228720ee08f59f Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sun, 20 Sep 2026 12:24:29 -0400 Subject: [PATCH 15/16] Preserve bound childcare attendance in annual projections --- docs/us-annual-static-aging.md | 8 +++ .../merge-readiness.md | 64 +++++++++++++++++++ .../microcosm/build/us_annual_static_aging.py | 44 ++++++++++--- .../tests/test_frame_serializer_registry.py | 5 +- .../tests/test_us_annual_static_aging.py | 61 ++++++++++++++++++ 5 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 experiments/us-childcare-attendance/merge-readiness.md diff --git a/docs/us-annual-static-aging.md b/docs/us-annual-static-aging.md index 554d8eacc..0a4fe250c 100644 --- a/docs/us-annual-static-aging.md +++ b/docs/us-annual-static-aging.md @@ -62,6 +62,14 @@ independent demographic, monetary, identity, and runtime acceptance checks. That process supplies the separate annual projection acceptance report and publication decision; this module supplies no certification override. +If the base contains NSECE childcare attendance, every annual file preserves the +original content-bound attendance receipt. Static aging holds ages and schedules +fixed while changing weights and monetary inputs; the writer verifies that those +bound values remain exact before finalizing a file. It rejects missing receipts +for nonzero attendance and detects altered ages, identities or attendance instead +of rebinding them. Each annual round-trip report includes the validated binding +hash. This preserves provenance, not evidence of attendance changes over time. + ## Qualification and publication order 1. Qualify the base release under its existing contract with the intended diff --git a/experiments/us-childcare-attendance/merge-readiness.md b/experiments/us-childcare-attendance/merge-readiness.md new file mode 100644 index 000000000..0483236fc --- /dev/null +++ b/experiments/us-childcare-attendance/merge-readiness.md @@ -0,0 +1,64 @@ +# PR #916 merge-readiness audit + +Objective: resolve the PR's engineering, integration and statistical issues to +reach merge-ready quality while retaining draft status. This audit does not +authorize publication or replacing the default population. Completion is not +yet established. + +## Current audit, September 20, 2026 + +The starting PR head is `d2601c7a0749b134822086f4783e03501c750e32`. +Its 24 GitHub checks passed. The current integration base is +`18c6d39a`, 65 main-branch commits beyond the PR's previous merge base. +Maria's September 15 review is the latest external review; no follow-up approval +is recorded. Passing tests below are engineering evidence, not statistical +acceptance. + +| Requirement | Current evidence and remaining work | +| --- | --- | +| Current main, clean merge and safe PR branch | Merge commit `8d74be68` reconciles README and serializer-test conflicts, preserving both childcare and annual projection changes. The feature branch tracks and pushes to `fork/fix/us-childcare-attendance`; verify the final remote state after pushing. | +| C1: exact attendance values and source recipe bound together | Existing binding covers source pins, code/runtime, settings, identities, ages, household membership and values. Native ingress, reuse and export regressions exist; final affected-suite validation is still required. | +| A1: row-complete final attendance | Attendance-specific validation and binding checks run at release coverage and final write. Verify failures cannot be waived or lost through later transformations. | +| A2: valid source household identities | Missing, blank, stringified-null and unresolved household identities are rejected; regression coverage exists. | +| A3: household intensity and larger-sibship validation | Joint days/hours and 3+ child diagnostics exist. Their reported screen failures remain substantive; adding diagnostics alone does not qualify the model. | +| S1: noncalendar transport and sensitivity | Observed regular hours remain fixed; paired sensitivity holds donor identities fixed. State flags and missing-calendar selection remain unresolved statistical evidence. | +| S2: explicit operation provenance | Receipts identify calendar derivation, dependence fitting, noncalendar bridge, target harmonization, joint transfer and outside-domain baseline policy. | +| Fiscal builder and exact-k integration | Repair metadata preservation and exact-k source configuration have targeted tests. A complete fiscal build using the actual target bundle and survey inputs remains required. | +| Annual projection compatibility | The regression reproduced the original receipt loss. All 42 annual/serializer tests now pass: the 2024 base and 2025/2026 projections retain their original receipt, changed attendance is refused before finalizing an output, and the combined serializer inventory has ten writers. | +| Source mapping and population integrity | Licensed DS4/DS5 files, pinned ASEC cache and BuildP parent are available locally. Current candidate evidence preserves original population values/weights. Recheck any affected evidence after implementation or runtime changes. | +| Statistical qualification | Existing experiments fail provisional subgroup/household screens. Do not redefine those failures as acceptance, relax thresholds after seeing results, or call inspected partitions independent validation. A defensible model/assumption treatment and stronger validation remain required. | +| Final tests, packaging, evidence and handoff | Run relevant engine and wheel checks; inspect their actual coverage. Keep source microdata and per-person hashes out of commits. Preserve the PR's feature-branch push target, draft status and VS Code PR file-tree view. | + +## September 20 engineering checks + +- Initial merged attendance, launcher and serializer run: 139 passed, one failed. + The failure was the serializer count: the combined branches contain ten + serializers, while each independently expected nine. The expectation was + corrected; the subsequent 42-test annual/serializer run passes. +- Specification coverage remains current: 42,159/42,159 fields and 41/41 inventory + checks. No checksum regeneration is needed for this merge. +- Repository lint and the tracked CI test inventory passed after conflict + resolution. +- The new annual receipt regression failed on the original annual writer at + native reload, proving the uncovered integration defect. The final annual and + serializer run passes all 42 tests with no skips. Repository-wide lint and + changed-file formatting also pass after the fix. +- VS Code recognizes the worktree as PR #916 and shows the GitHub Pull Request + sidebar with its clickable file tree. Its Sync target is the fork's feature + branch, not main. + +## Full-build preparation and constraints + +The pinned DS4/DS5 inputs, ASEC source cache and original BuildP parent are +available locally. A 2023 Chronicle bundle build is in progress using Chronicle +commit `78466057401af48f9a53da41b87295241e4af1ba`; no completed target bundle or +successful fiscal build is claimed yet. + +This host has 16 GiB RAM. The repository's measured system requirements put the +national build floor at 32 GiB, with approximately 15 GiB needed for SCF +imputation alone. A full national build needs an appropriately sized build host; +the small-fixture annual tests do not establish national-build feasibility. + +The full fiscal run is not replaced by the standalone attendance-preparation +command or by mocked builder tests. Publication, survey transport validity and +maintainer approval are distinct from serializer and code-contract checks. diff --git a/packages/microcosm-build/src/microcosm/build/us_annual_static_aging.py b/packages/microcosm-build/src/microcosm/build/us_annual_static_aging.py index 1022364ec..65815cdae 100644 --- a/packages/microcosm-build/src/microcosm/build/us_annual_static_aging.py +++ b/packages/microcosm-build/src/microcosm/build/us_annual_static_aging.py @@ -21,6 +21,11 @@ import pandas as pd +from microcosm.build.us_runtime.childcare_attendance_receipt import ( + ATTENDANCE_RECEIPT_KEY, + restore_native_childcare_receipt, + write_native_childcare_receipt, +) from microcosm.calibrate import ( SeriesProjection, ssa_population_projection, @@ -178,10 +183,30 @@ def _verify(path: Path, tables: Mapping[str, pd.DataFrame], year: int) -> dict: pd.testing.assert_frame_equal( getattr(native, entity), external, check_dtype=False, check_exact=True ) - return {"logical_tables": True, "native_loader": True, "time_period": year} + # Static aging retains ages, identities, and attendance; changing weights or + # monetary inputs must neither discard nor reseal the original receipt. + weights = Weights( + tables["household"]["household_weight"].to_numpy(dtype=float), + WeightKind.CALIBRATED, + ) + verified = restore_native_childcare_receipt( + path, Frame(tables, US_SCHEMA, {"household": weights}) + ) + receipt = {"logical_tables": True, "native_loader": True, "time_period": year} + if ATTENDANCE_RECEIPT_KEY in verified.metadata: + receipt["childcare_attendance_binding_sha256"] = verified.metadata[ + ATTENDANCE_RECEIPT_KEY + ]["binding_sha256"] + return receipt -def _write_year(path: Path, tables: Mapping[str, pd.DataFrame], year: int) -> dict: +def _write_year( + path: Path, + tables: Mapping[str, pd.DataFrame], + year: int, + *, + frame_metadata: Mapping[str, Any] | None = None, +) -> dict: temporary = path.with_suffix(".tmp.h5") with pd.HDFStore(temporary, "w") as store: for entity, table in tables.items(): @@ -189,6 +214,8 @@ def _write_year(path: Path, tables: Mapping[str, pd.DataFrame], year: int) -> di store, entity, table, preferred_format="table", data_columns=True ) store.put("_time_period", pd.Series([year]), format="table") + if frame_metadata and ATTENDANCE_RECEIPT_KEY in frame_metadata: + write_native_childcare_receipt(temporary, frame_metadata) receipt = _verify(temporary, tables, year) temporary.replace(path) return receipt @@ -261,13 +288,14 @@ def build_annual_static_aging( frame = Frame( tables, US_SCHEMA, {"household": Weights(weights, WeightKind.CALIBRATED)} ) + frame = restore_native_childcare_receipt(base, frame) base_tables = { entity: table.loc[:, columns[entity]] for entity, table in engine_tables( frame, weighted_entities=("household",) ).items() } - _verify(base, base_tables, base_year) + base_round_trip = _verify(base, base_tables, base_year) del tables, base_tables demographics = ssa_population_projection(ssa, age_top=85, age_bands={80: 84}) for year in range(base_year, end_year + 1): @@ -332,11 +360,7 @@ def build_annual_static_aging( if year == base_year: shutil.copyfile(base, path) _check_pin(path, base_sha256) - round_trip = { - "logical_tables": True, - "native_loader": True, - "time_period": year, - } + round_trip = dict(base_round_trip) projection_receipt = None else: result = static_aging( @@ -363,7 +387,9 @@ def build_annual_static_aging( entity: getattr(dataset, entity).loc[:, columns[entity]] for entity in frame.entities } - round_trip = _write_year(path, projected_tables, year) + round_trip = _write_year( + path, projected_tables, year, frame_metadata=frame.metadata + ) receipt_path = output / f"projection_{year}.json" _json( receipt_path, diff --git a/packages/microcosm-build/tests/test_frame_serializer_registry.py b/packages/microcosm-build/tests/test_frame_serializer_registry.py index 30a7444f2..f2c5004ce 100644 --- a/packages/microcosm-build/tests/test_frame_serializer_registry.py +++ b/packages/microcosm-build/tests/test_frame_serializer_registry.py @@ -378,15 +378,14 @@ def _round_trip_us_annual_static_aging( ) -> BooleanRoundTrip: pytest.importorskip("policyengine_us") from microcosm.build.us_annual_static_aging import _write_year + from microcosm.frame.materialize import engine_tables source = _dtype_family_table(nullable_case) before = source.copy(deep=True) frame = _us_frame(source) path = tmp_path / "annual.h5" try: - _write_year( - path, {entity: frame.table(entity) for entity in frame.entities}, 2025 - ) + _write_year(path, engine_tables(frame, weighted_entities=("household",)), 2025) finally: pd.testing.assert_frame_equal( source, before, check_exact=True, check_dtype=True diff --git a/packages/microcosm-build/tests/test_us_annual_static_aging.py b/packages/microcosm-build/tests/test_us_annual_static_aging.py index 5c8ad848b..20128cc3c 100644 --- a/packages/microcosm-build/tests/test_us_annual_static_aging.py +++ b/packages/microcosm-build/tests/test_us_annual_static_aging.py @@ -146,6 +146,67 @@ def test_small_actual_calibration_exports_independent_years(inputs): assert receipt["column_series"]["employment_income_before_lsr"] in receipt["totals"] +def test_annual_projection_preserves_bound_attendance(inputs): + from microcosm.build.us_runtime.childcare_attendance import ( + US_CHILDCARE_ATTENDANCE_COLUMNS, + ) + from microcosm.build.us_runtime.childcare_attendance_receipt import ( + ATTENDANCE_RECEIPT_KEY, + assert_bound_childcare_attendance, + bind_childcare_attendance, + write_native_childcare_receipt, + ) + from microcosm.build.us_runtime.l0_refit_export import load_us_frame + from microcosm.frame import Frame + + base = inputs["base_h5"] + frame = load_us_frame(base) + person = frame.table("person") + for column, values in zip( + US_CHILDCARE_ATTENDANCE_COLUMNS, + ([22.0, 0.0, 13.0], [5.0, 0.0, 3.0], [8.0, 0.0, 4.0]), + strict=True, + ): + person[column] = values + frame = bind_childcare_attendance( + Frame( + {e: frame.table(e) for e in frame.entities}, + frame.schema, + {"household": frame.weights_for("household")}, + metadata={"nsece_childcare_attendance": {"synthetic_fixture": True}}, + ) + ) + with pd.HDFStore(base, "a") as store: + put_frame_table( + store, "person", person, preferred_format="table", data_columns=True + ) + write_native_childcare_receipt(base, frame.metadata) + inputs["base_sha256"] = annual._sha256(base) + annual.build_annual_static_aging(**inputs) + for year in (2024, 2025, 2026): + projected = load_us_frame(inputs["output_dir"] / f"populace_us_{year}.h5") + assert_bound_childcare_attendance(projected, require_stage=False) + assert ( + projected.metadata[ATTENDANCE_RECEIPT_KEY] + == frame.metadata[ATTENDANCE_RECEIPT_KEY] + ) + pd.testing.assert_frame_equal( + projected.table("person")[list(US_CHILDCARE_ATTENDANCE_COLUMNS)], + person[list(US_CHILDCARE_ATTENDANCE_COLUMNS)], + ) + # A changed schedule must fail before the annual file is finalized, even + # when the supplied receipt itself is valid for the original population. + from microcosm.frame.materialize import engine_tables + + tables = engine_tables(frame, weighted_entities=("household",)) + tables["person"] = tables["person"].copy() + tables["person"].loc[0, "childcare_days_per_week"] = 4 + invalid_output = inputs["output_dir"] / "invalid.h5" + with pytest.raises(ValueError, match="differ from the source receipt"): + annual._write_year(invalid_output, tables, 2027, frame_metadata=frame.metadata) + assert not invalid_output.exists() + + def test_base_h5_symlink_to_extensionless_cache_blob(inputs): base = inputs["base_h5"] blob = base.with_name("cached-blob-without-extension") From 25a54a4e0e27d8cf5261d5d124b510c4d281835b Mon Sep 17 00:00:00 2001 From: ZimingHua Date: Sun, 20 Sep 2026 12:46:22 -0400 Subject: [PATCH 16/16] Record verified attendance checks and raw-build blockers --- docs/us-childcare-attendance.md | 4 +- .../merge-readiness.md | 116 ++++++++++++++++-- 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/docs/us-childcare-attendance.md b/docs/us-childcare-attendance.md index dd361132e..4eef2567f 100644 --- a/docs/us-childcare-attendance.md +++ b/docs/us-childcare-attendance.md @@ -221,7 +221,9 @@ writing. Generic coverage overrides cannot waive this check. The written native file receives the receipt and is reloaded and checked before source evidence is reported. The receipt key holds only the attendance context and binding, never other frame metadata, and adding it does not rewrite any entity table. The L0 -refit export carries the receipt forward. Private per-person hashes stay in local checkpoints/H5; public reports +refit export carries the receipt forward, as do +[annual static-aging exports](us-annual-static-aging.md), which retain and check +the original ages and attendance. Private per-person hashes stay in local checkpoints/H5; public reports contain only aggregate receipt summaries. The private inventory is a sequence of ID/hash pairs: population-sized dictionaries cause quadratic traversal in the Frame metadata container, which is intended for small mappings. These hashes detect accidental stale diff --git a/experiments/us-childcare-attendance/merge-readiness.md b/experiments/us-childcare-attendance/merge-readiness.md index 0483236fc..7fad57770 100644 --- a/experiments/us-childcare-attendance/merge-readiness.md +++ b/experiments/us-childcare-attendance/merge-readiness.md @@ -16,18 +16,18 @@ acceptance. | Requirement | Current evidence and remaining work | | --- | --- | -| Current main, clean merge and safe PR branch | Merge commit `8d74be68` reconciles README and serializer-test conflicts, preserving both childcare and annual projection changes. The feature branch tracks and pushes to `fork/fix/us-childcare-attendance`; verify the final remote state after pushing. | -| C1: exact attendance values and source recipe bound together | Existing binding covers source pins, code/runtime, settings, identities, ages, household membership and values. Native ingress, reuse and export regressions exist; final affected-suite validation is still required. | -| A1: row-complete final attendance | Attendance-specific validation and binding checks run at release coverage and final write. Verify failures cannot be waived or lost through later transformations. | +| Current main, clean merge and safe PR branch | Merge commit `8d74be68` reconciles README and serializer-test conflicts. At `4f746222`, local HEAD, upstream tracking and push refs match `fork/fix/us-childcare-attendance`; GitHub reports mergeable and draft. | +| C1: exact attendance values and source recipe bound together | The 573-test affected suite passes with no skips. Fresh verification of the real 166,321-person candidate through both native loaders validates the exact source/code/runtime/settings binding and preservation of all original values and weights. | +| A1: row-complete final attendance | The affected suite covers the final rowwise boundary (null days, out-of-range days, incoherent zeros and fractional monthly days), unbound nondegenerate attendance refusal, native tampering, receipt reuse and fiscal-builder checks. The complete national fiscal run remains separate evidence below. | | A2: valid source household identities | Missing, blank, stringified-null and unresolved household identities are rejected; regression coverage exists. | | A3: household intensity and larger-sibship validation | Joint days/hours and 3+ child diagnostics exist. Their reported screen failures remain substantive; adding diagnostics alone does not qualify the model. | | S1: noncalendar transport and sensitivity | Observed regular hours remain fixed; paired sensitivity holds donor identities fixed. State flags and missing-calendar selection remain unresolved statistical evidence. | | S2: explicit operation provenance | Receipts identify calendar derivation, dependence fitting, noncalendar bridge, target harmonization, joint transfer and outside-domain baseline policy. | -| Fiscal builder and exact-k integration | Repair metadata preservation and exact-k source configuration have targeted tests. A complete fiscal build using the actual target bundle and survey inputs remains required. | +| Fiscal builder and exact-k integration | Repair metadata preservation and exact-k source configuration have targeted tests. The release rule requires a build from raw sources and certification from this PR's tree; a supplied-parent fiscal run is an intermediate integration check only. | | Annual projection compatibility | The regression reproduced the original receipt loss. All 42 annual/serializer tests now pass: the 2024 base and 2025/2026 projections retain their original receipt, changed attendance is refused before finalizing an output, and the combined serializer inventory has ten writers. | | Source mapping and population integrity | Licensed DS4/DS5 files, pinned ASEC cache and BuildP parent are available locally. Current candidate evidence preserves original population values/weights. Recheck any affected evidence after implementation or runtime changes. | | Statistical qualification | Existing experiments fail provisional subgroup/household screens. Do not redefine those failures as acceptance, relax thresholds after seeing results, or call inspected partitions independent validation. A defensible model/assumption treatment and stronger validation remain required. | -| Final tests, packaging, evidence and handoff | Run relevant engine and wheel checks; inspect their actual coverage. Keep source microdata and per-person hashes out of commits. Preserve the PR's feature-branch push target, draft status and VS Code PR file-tree view. | +| Final tests, packaging, evidence and handoff | The affected engine and wheel suites pass as detailed below; the real candidate report is reproduced. Source microdata and per-person hashes remain local. The PR retains its feature-branch push target, draft status and VS Code PR file-tree view. Full-build and statistical evidence remain separate requirements. | ## September 20 engineering checks @@ -46,18 +46,108 @@ acceptance. - VS Code recognizes the worktree as PR #916 and shows the GitHub Pull Request sidebar with its clickable file tree. Its Sync target is the fork's feature branch, not main. +- At `4f746222`, all 573 affected source, receipt, coverage, fiscal-builder, + exact-k-launcher and L0-export tests pass, with no skips. +- All workspace wheels build and install into a separate constrained environment. + The five package imports resolve inside that environment, PolicyEngine-US is + absent as required by the base-wheel lane, and the annual exporter matches + the committed source bytes. The affected wheel suite passes 172 tests; its + 40 skips comprise 26 checks requiring PolicyEngine-US and 14 requiring + optional PyTables. The engine-enabled tests above cover those boundaries. +- Fresh native candidate verification reproduces + [the committed report](review-fixes-verification.json) byte for byte + (SHA256 `765ef64f53b7cad45153bc96ffae65b6156f7c23259f004ad722c828edea165e`). + Both native loaders preserve all 166,321 people's original values and weights, + and the exact attendance binding. No recipe change or population rebuild was + necessary for the main merge and annual receipt fix. ## Full-build preparation and constraints -The pinned DS4/DS5 inputs, ASEC source cache and original BuildP parent are -available locally. A 2023 Chronicle bundle build is in progress using Chronicle -commit `78466057401af48f9a53da41b87295241e4af1ba`; no completed target bundle or -successful fiscal build is claimed yet. +The [US release build rule](../../docs/us-release-build-rule.md) requires a +from-scratch build and certification receipt from the PR's own tree for a change +that alters how the dataset is built. Reusing the July BuildP population does +not satisfy that requirement, even if its fiscal refresh passes. Default +promotion additionally requires the exact-k frozen-register improvement gate; +publication remains a separate human decision. -This host has 16 GiB RAM. The repository's measured system requirements put the -national build floor at 32 GiB, with approximately 15 GiB needed for SCF -imputation alone. A full national build needs an appropriately sized build host; -the small-fixture annual tests do not establish national-build feasibility. +The pinned DS4/DS5 files, raw ASEC CSV cache and original BuildP parent are +available locally. These are not the complete processed inputs for the raw +base builder. The inspected feed and source-stage remedies are still in open +upstream PRs as of September 20: + +- [#952](https://github.com/PolicyEngine/microcosm/pull/952) pins and fetches the + three processed ASEC H5 inputs from their public mirror. +- [#955](https://github.com/PolicyEngine/microcosm/pull/955) reproduces the + approved Chronicle feed's 586 recordset/period pairs with labelled facts. + [#961](https://github.com/PolicyEngine/microcosm/pull/961), stacked on #955, + pins the validated consumer artifact produced with + [Chronicle #278](https://github.com/PolicyEngine/chronicle/pull/278). +- [#948](https://github.com/PolicyEngine/microcosm/pull/948) diagnoses unresolved + SPM composition, and [#959](https://github.com/PolicyEngine/microcosm/pull/959), + stacked on #948, materializes the Census independence role in raw builds. + +Those implementations have not been incorporated into this PR. The approved +target scope spans multiple years. Both exploratory Chronicle builds were +deliberately stopped: the default run included other countries, and the US-only +2023 run did not reproduce the approved multi-period scope. Their partial +outputs are not accepted consumer artifacts. A generic current-year export +must not silently replace the reviewed feed. + +This host has 16 GiB RAM. The release runbook records a raw base build peaking at +72.47 GB on a 128 GiB machine under the older 1.819.0 engine; it also reports a +July fiscal comparator at about 85 GB. These historical measurements are not a +guarantee for the present larger build, but they rule out treating a generic +32 GiB minimum as adequate preparation here. An appropriately provisioned build +host and the licensed raw inputs are still needed. + +Once the upstream prerequisites are integrated, validation must build a fresh +raw-source base/pool and run the normal preflight and certification gates from +the resulting PR tree, retaining its code, input and artifact identities. The +July frozen household selection cannot be reused for the rebuilt base; the +release runbook explains its donor-loss failure and the permitted new lineage. +No coverage or statistical gate overrides are an acceptance substitute. + +The invocation below is only a prepared **supplied-parent integration check**, +not the required from-scratch build and not a completed run. `/local` denotes +the operator's input/output directory; use a fresh output directory, the +recorded parent hash, and replace the quoted SHA256 placeholders with the actual +qualified consumer-artifact hashes. + +```bash +uv run --no-sync python tools/build_us_fiscal_refresh_release.py \ + --base-h5 /local/populace_us_2024.h5 \ + --ledger-facts /local/chronicle-us-consumer-artifact \ + --ledger-facts-sha256 "CONSUMER_FACTS_SHA256" \ + --ledger-manifest-sha256 "CONSUMER_MANIFEST_SHA256" \ + --asec-2023-weeks-unemployed-source /local/asec/asecpub23csv.zip \ + --childcare-attendance-household-tsv /local/39466-0005-Data.tsv \ + --childcare-attendance-calendar-tsv /local/39466-0004-Data.tsv \ + --childcare-attendance-asec-cache /local/asec \ + --childcare-attendance-inherit-outside-domain-baseline \ + --seed 915 --no-staging --out /local/pr916-fiscal-validation +``` + +That integration check should retain its final H5, source-coverage receipt and +release-gate results, with attendance checked on native reload. Completion of +the merge requirement needs equivalent evidence from the raw-source build and +certification as well. Neither an authenticated target registry nor the +supplied-parent integration result alone meets the requirement. + +## Independent attendance evidence + +Recent SIPP files are not a replacement hours benchmark. The Census Bureau's +[2023 SIPP comparability note](https://www.census.gov/programs-surveys/sipp/tech-documentation/user-notes/2023-usernotes/compr-2023-sipp-chld-care-data-prev-yrs.html) +describes the change from each child's arrangement hours in the 2008 and earlier +panels to the redesigned arrangement questions. The +[2024 SIPP variable catalog](https://api.census.gov/data/2024/sipp/variables.html) +provides arrangement and payment variables, not the joint days/hours calendar +needed here. It could support a separately harmonized participation comparison; +it cannot close the schedule-intensity or noncalendar-transport evidence gap. + +The existing NSECE development folds and incomplete-calendar bounds remain useful +diagnostics. They are not a new independent sample, and missing schedules cannot +be turned into observed validation outcomes. The subgroup and household screen +failures therefore remain open after the engineering checks pass. The full fiscal run is not replaced by the standalone attendance-preparation command or by mocked builder tests. Publication, survey transport validity and