From 821414eec752a815b7a2d35c43e597ad1ecf8747 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 5 Dec 2025 11:22:00 -0500 Subject: [PATCH 01/24] Add sparse matrix builder for local area calibration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core components: - sparse_matrix_builder.py: Database-driven approach for building calibration matrices - calibration_utils.py: Shared utilities (cache clearing, constraints, geo helpers) - matrix_tracer.py: Debugging utility for tracing through sparse matrices - create_stratified_cps.py: Create stratified sample preserving high-income households - test_sparse_matrix_builder.py: 6 verification tests for matrix correctness Data pipeline changes: - Add GEO_STACKING env var to cps.py and puf.py for geo-stacking data generation - Add GEO_STACKING_MODE env var to extended_cps.py - Add CPS_2024_Full, PUF_2023, ExtendedCPS_2023 classes - Add policy_data.db download to prerequisites - Add 'make data-geo' target for geo-stacking data pipeline CI/CD: - Add geo-stacking dataset build step to workflow - Add sparse matrix builder test step after geo data generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/reusable_test.yaml | 12 + Makefile | 6 + policyengine_us_data/datasets/cps/cps.py | 15 + .../datasets/cps/extended_cps.py | 20 +- .../cps/local_area_calibration/__init__.py | 0 .../calibration_utils.py | 67 +++ .../create_stratified_cps.py | 307 ++++++++++ .../local_area_calibration/matrix_tracer.py | 306 ++++++++++ .../sparse_matrix_builder.py | 186 ++++++ .../test_sparse_matrix_builder.py | 542 ++++++++++++++++++ policyengine_us_data/datasets/puf/puf.py | 19 +- .../storage/download_private_prerequisites.py | 6 + 12 files changed, 1481 insertions(+), 5 deletions(-) create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/__init__.py create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py diff --git a/.github/workflows/reusable_test.yaml b/.github/workflows/reusable_test.yaml index 99cfff16..4618f775 100644 --- a/.github/workflows/reusable_test.yaml +++ b/.github/workflows/reusable_test.yaml @@ -75,6 +75,18 @@ jobs: TEST_LITE: ${{ !inputs.upload_data }} PYTHON_LOG_LEVEL: INFO + - name: Build geo-stacking datasets + if: inputs.full_suite + run: | + GEO_STACKING=true python policyengine_us_data/datasets/cps/cps.py + GEO_STACKING=true python policyengine_us_data/datasets/puf/puf.py + GEO_STACKING_MODE=true python policyengine_us_data/datasets/cps/extended_cps.py + python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 + + - name: Run sparse matrix builder tests + if: inputs.full_suite + run: pytest policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py -v + - name: Save calibration log if: inputs.full_suite uses: actions/upload-artifact@v4 diff --git a/Makefile b/Makefile index 78d0904d..d8b127b0 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,12 @@ data: mv policyengine_us_data/storage/enhanced_cps_2024.h5 policyengine_us_data/storage/dense_enhanced_cps_2024.h5 cp policyengine_us_data/storage/sparse_enhanced_cps_2024.h5 policyengine_us_data/storage/enhanced_cps_2024.h5 +data-geo: data + GEO_STACKING=true python policyengine_us_data/datasets/cps/cps.py + GEO_STACKING=true python policyengine_us_data/datasets/puf/puf.py + GEO_STACKING_MODE=true python policyengine_us_data/datasets/cps/extended_cps.py + python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 + clean: rm -f policyengine_us_data/storage/*.h5 rm -f policyengine_us_data/storage/*.db diff --git a/policyengine_us_data/datasets/cps/cps.py b/policyengine_us_data/datasets/cps/cps.py index f932e0d5..aa456f23 100644 --- a/policyengine_us_data/datasets/cps/cps.py +++ b/policyengine_us_data/datasets/cps/cps.py @@ -2058,6 +2058,15 @@ class CPS_2023_Full(CPS): time_period = 2023 +class CPS_2024_Full(CPS): + name = "cps_2024_full" + label = "CPS 2024 (full)" + raw_cps = CensusCPS_2024 + previous_year_raw_cps = CensusCPS_2023 + file_path = STORAGE_FOLDER / "cps_2024_full.h5" + time_period = 2024 + + class PooledCPS(Dataset): data_format = Dataset.ARRAYS input_datasets: list @@ -2117,10 +2126,15 @@ class Pooled_3_Year_CPS_2023(PooledCPS): url = "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5" +geo_stacking = os.environ.get("GEO_STACKING") == "true" + if __name__ == "__main__": if test_lite: + CPS_2023().generate() CPS_2024().generate() CPS_2025().generate() + elif geo_stacking: + CPS_2023_Full().generate() else: CPS_2021().generate() CPS_2022().generate() @@ -2130,4 +2144,5 @@ class Pooled_3_Year_CPS_2023(PooledCPS): CPS_2021_Full().generate() CPS_2022_Full().generate() CPS_2023_Full().generate() + CPS_2024_Full().generate() Pooled_3_Year_CPS_2023().generate() diff --git a/policyengine_us_data/datasets/cps/extended_cps.py b/policyengine_us_data/datasets/cps/extended_cps.py index f28c726c..e83b7015 100644 --- a/policyengine_us_data/datasets/cps/extended_cps.py +++ b/policyengine_us_data/datasets/cps/extended_cps.py @@ -320,8 +320,17 @@ def impute_income_variables( return result +class ExtendedCPS_2023(ExtendedCPS): + cps = CPS_2023_Full + puf = PUF_2023 + name = "extended_cps_2023" + label = "Extended CPS (2023)" + file_path = STORAGE_FOLDER / "extended_cps_2023.h5" + time_period = 2023 + + class ExtendedCPS_2024(ExtendedCPS): - cps = CPS_2024 + cps = CPS_2024_Full puf = PUF_2024 name = "extended_cps_2024" label = "Extended CPS (2024)" @@ -330,4 +339,11 @@ class ExtendedCPS_2024(ExtendedCPS): if __name__ == "__main__": - ExtendedCPS_2024().generate() + geo_stacking_mode = ( + os.environ.get("GEO_STACKING_MODE", "").lower() == "true" + ) + + if geo_stacking_mode: + ExtendedCPS_2023().generate() + else: + ExtendedCPS_2024().generate() diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/__init__.py b/policyengine_us_data/datasets/cps/local_area_calibration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py new file mode 100644 index 00000000..cce627e2 --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -0,0 +1,67 @@ +""" +Shared utilities for calibration scripts. +""" + +from typing import List +import numpy as np + + +def get_calculated_variables(sim) -> List[str]: + """ + Return variables that should be cleared for state-swap recalculation. + + Includes variables with formulas, adds, or subtracts. + + Excludes ID variables (person_id, household_id, etc.) because: + 1. They have formulas that generate sequential IDs (0, 1, 2, ...) + 2. We need the original H5 values, not regenerated sequences + 3. PolicyEngine's random() function uses entity IDs as seeds: + seed = abs(entity_id * 100 + count_random_calls) + If IDs change, random-dependent variables (SSI resource test, + WIC nutritional risk, WIC takeup) produce different results. + """ + exclude_ids = {'person_id', 'household_id', 'tax_unit_id', 'spm_unit_id', + 'family_id', 'marital_unit_id'} + return [name for name, var in sim.tax_benefit_system.variables.items() + if (var.formulas or getattr(var, 'adds', None) or getattr(var, 'subtracts', None)) + and name not in exclude_ids] + + +def apply_op(values: np.ndarray, op: str, val: str) -> np.ndarray: + """Apply constraint operation to values array.""" + try: + parsed = float(val) + if parsed.is_integer(): + parsed = int(parsed) + except ValueError: + if val == 'True': + parsed = True + elif val == 'False': + parsed = False + else: + parsed = val + + if op in ('==', '='): + return values == parsed + if op == '>': + return values > parsed + if op == '>=': + return values >= parsed + if op == '<': + return values < parsed + if op == '<=': + return values <= parsed + if op == '!=': + return values != parsed + return np.ones(len(values), dtype=bool) + + +def _get_geo_level(geo_id) -> int: + """Return geographic level: 0=National, 1=State, 2=District.""" + if geo_id == 'US': + return 0 + try: + val = int(geo_id) + return 1 if val < 100 else 2 + except (ValueError, TypeError): + return 3 diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py new file mode 100644 index 00000000..d1066060 --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py @@ -0,0 +1,307 @@ +""" +Create a stratified sample of extended_cps_2023.h5 that preserves high-income households. +This is needed for congressional district geo-stacking where the full dataset is too large. + +Strategy: +- Keep ALL households above a high income threshold (e.g., top 1%) +- Sample progressively less from lower income strata +- Ensure representation across all income levels +""" + +import numpy as np +import pandas as pd +import h5py +from policyengine_us import Microsimulation +from policyengine_core.data.dataset import Dataset +from policyengine_core.enums import Enum + + +def create_stratified_cps_dataset( + target_households=30_000, + high_income_percentile=99, # Keep ALL households above this percentile + base_dataset="hf://policyengine/test/extended_cps_2023.h5", + output_path=None, +): + """ + Create a stratified sample of CPS data preserving high-income households. + + Args: + target_households: Target number of households in output (approximate) + high_income_percentile: Keep ALL households above this AGI percentile + output_path: Where to save the stratified h5 file + """ + print("\n" + "=" * 70) + print("CREATING STRATIFIED CPS DATASET") + print("=" * 70) + + # Load the original simulation + print("Loading original dataset...") + sim = Microsimulation(dataset=base_dataset) + + # Calculate AGI for all households + print("Calculating household AGI...") + agi = sim.calculate("adjusted_gross_income", map_to="household").values + household_ids = sim.calculate("household_id", map_to="household").values + n_households_orig = len(household_ids) + + print(f"Original dataset: {n_households_orig:,} households") + print(f"Target dataset: {target_households:,} households") + print(f"Reduction ratio: {target_households/n_households_orig:.1%}") + + # Calculate AGI percentiles + print("\nAnalyzing income distribution...") + percentiles = [0, 25, 50, 75, 90, 95, 99, 99.5, 99.9, 100] + agi_percentiles = np.percentile(agi, percentiles) + + print("AGI Percentiles:") + for p, val in zip(percentiles, agi_percentiles): + print(f" {p:5.1f}%: ${val:,.0f}") + + # Define sampling strategy + # Keep ALL high earners, sample progressively less from lower strata + high_income_threshold = np.percentile(agi, high_income_percentile) + print( + f"\nHigh-income threshold (top {100-high_income_percentile}%): ${high_income_threshold:,.0f}" + ) + + # Create strata with sampling rates + strata = [ + (99.9, 100, 1.00), # Top 0.1% - keep ALL + (99.5, 99.9, 1.00), # 99.5-99.9% - keep ALL + (99, 99.5, 1.00), # 99-99.5% - keep ALL + (95, 99, 0.80), # 95-99% - keep 80% + (90, 95, 0.60), # 90-95% - keep 60% + (75, 90, 0.40), # 75-90% - keep 40% + (50, 75, 0.25), # 50-75% - keep 25% + (25, 50, 0.15), # 25-50% - keep 15% + (0, 25, 0.10), # Bottom 25% - keep 10% + ] + + # Adjust sampling rates to hit target + print("\nInitial sampling strategy:") + expected_count = 0 + for low_p, high_p, rate in strata: + low_val = np.percentile(agi, low_p) if low_p > 0 else -np.inf + high_val = np.percentile(agi, high_p) if high_p < 100 else np.inf + in_stratum = np.sum((agi > low_val) & (agi <= high_val)) + expected = int(in_stratum * rate) + expected_count += expected + print( + f" {low_p:5.1f}-{high_p:5.1f}%: {in_stratum:6,} households x {rate:.0%} = {expected:6,}" + ) + + print(f"Expected total: {expected_count:,} households") + + # Adjust rates if needed + if expected_count > target_households * 1.1: # Allow 10% overage + adjustment = target_households / expected_count + print( + f"\nAdjusting rates by factor of {adjustment:.2f} to meet target..." + ) + + # Never reduce the top percentiles + strata_adjusted = [] + for low_p, high_p, rate in strata: + if high_p >= 99: # Never reduce top 1% + strata_adjusted.append((low_p, high_p, rate)) + else: + strata_adjusted.append( + (low_p, high_p, min(1.0, rate * adjustment)) + ) + strata = strata_adjusted + + # Select households based on strata + print("\nSelecting households...") + selected_mask = np.zeros(n_households_orig, dtype=bool) + + for low_p, high_p, rate in strata: + low_val = np.percentile(agi, low_p) if low_p > 0 else -np.inf + high_val = np.percentile(agi, high_p) if high_p < 100 else np.inf + + in_stratum = (agi > low_val) & (agi <= high_val) + stratum_indices = np.where(in_stratum)[0] + n_in_stratum = len(stratum_indices) + + if rate >= 1.0: + # Keep all + selected_mask[stratum_indices] = True + n_selected = n_in_stratum + else: + # Random sample within stratum + n_to_select = int(n_in_stratum * rate) + if n_to_select > 0: + np.random.seed(42) # For reproducibility + selected_indices = np.random.choice( + stratum_indices, n_to_select, replace=False + ) + selected_mask[selected_indices] = True + n_selected = n_to_select + else: + n_selected = 0 + + print( + f" {low_p:5.1f}-{high_p:5.1f}%: Selected {n_selected:6,} / {n_in_stratum:6,} ({n_selected/max(1,n_in_stratum):.0%})" + ) + + n_selected = np.sum(selected_mask) + print( + f"\nTotal selected: {n_selected:,} households ({n_selected/n_households_orig:.1%} of original)" + ) + + # Verify high earners are preserved + high_earners_mask = agi >= high_income_threshold + n_high_earners = np.sum(high_earners_mask) + n_high_earners_selected = np.sum(selected_mask & high_earners_mask) + print(f"\nHigh earners (>=${high_income_threshold:,.0f}):") + print(f" Original: {n_high_earners:,}") + print( + f" Selected: {n_high_earners_selected:,} ({n_high_earners_selected/n_high_earners:.0%})" + ) + + # Get the selected household IDs + selected_household_ids = set(household_ids[selected_mask]) + + # Now filter the dataset using DataFrame approach (similar to create_sparse_state_stacked.py) + print("\nCreating filtered dataset...") + time_period = int(sim.default_calculation_period) + + # Convert full simulation to DataFrame + df = sim.to_input_dataframe() + + # Filter to selected households + hh_id_col = f"household_id__{time_period}" + df_filtered = df[df[hh_id_col].isin(selected_household_ids)].copy() + + print(f"Filtered DataFrame: {len(df_filtered):,} persons") + + # Create Dataset from filtered DataFrame + print("Creating Dataset from filtered DataFrame...") + stratified_dataset = Dataset.from_dataframe(df_filtered, time_period) + + # Build a simulation to convert to h5 + print("Building simulation from Dataset...") + stratified_sim = Microsimulation() + stratified_sim.dataset = stratified_dataset + stratified_sim.build_from_dataset() + + # Generate output path if not provided + if output_path is None: + from policyengine_us_data.storage import STORAGE_FOLDER + output_path = str(STORAGE_FOLDER / "stratified_extended_cps_2023.h5") + + # Save to h5 file + print(f"\nSaving to {output_path}...") + data = {} + + # Only save input variables (not calculated/derived variables) + input_vars = set(stratified_sim.input_variables) + print(f"Found {len(input_vars)} input variables (excluding calculated variables)") + + for variable in stratified_sim.tax_benefit_system.variables: + if variable not in input_vars: + continue + + data[variable] = {} + for period in stratified_sim.get_holder(variable).get_known_periods(): + values = stratified_sim.get_holder(variable).get_array(period) + + # Handle different value types + if variable == "county_fips": + values = values.astype("int32") + elif stratified_sim.tax_benefit_system.variables.get( + variable + ).value_type in (Enum, str): + # Check if it's an EnumArray with decode_to_str method + if hasattr(values, "decode_to_str"): + values = values.decode_to_str().astype("S") + else: + # Already a numpy array, just ensure it's string type + values = values.astype("S") + else: + values = np.array(values) + + if values is not None: + data[variable][period] = values + + if len(data[variable]) == 0: + del data[variable] + + # Write to h5 + with h5py.File(output_path, "w") as f: + for variable, periods in data.items(): + grp = f.create_group(variable) + for period, values in periods.items(): + grp.create_dataset(str(period), data=values) + + print(f"Stratified CPS dataset saved successfully!") + + # Verify the saved file + print("\nVerifying saved file...") + with h5py.File(output_path, "r") as f: + if "household_id" in f and str(time_period) in f["household_id"]: + hh_ids = f["household_id"][str(time_period)][:] + print(f" Final households: {len(hh_ids):,}") + if "person_id" in f and str(time_period) in f["person_id"]: + person_ids = f["person_id"][str(time_period)][:] + print(f" Final persons: {len(person_ids):,}") + if ( + "household_weight" in f + and str(time_period) in f["household_weight"] + ): + weights = f["household_weight"][str(time_period)][:] + print(f" Final household weights sum: {np.sum(weights):,.0f}") + + # Final income distribution check + print("\nVerifying income distribution in stratified dataset...") + stratified_sim_verify = Microsimulation(dataset=output_path) + agi_stratified = stratified_sim_verify.calculate( + "adjusted_gross_income", map_to="household" + ).values + + print("AGI Percentiles in stratified dataset:") + for p in [0, 25, 50, 75, 90, 95, 99, 99.5, 99.9, 100]: + val = np.percentile(agi_stratified, p) + print(f" {p:5.1f}%: ${val:,.0f}") + + max_agi_original = np.max(agi) + max_agi_stratified = np.max(agi_stratified) + print(f"\nMaximum AGI:") + print(f" Original: ${max_agi_original:,.0f}") + print(f" Stratified: ${max_agi_stratified:,.0f}") + + if max_agi_stratified < max_agi_original * 0.9: + print("WARNING: May have lost some ultra-high earners!") + else: + print("Ultra-high earners preserved!") + + return output_path + + +if __name__ == "__main__": + import sys + + # Parse command line arguments + if len(sys.argv) > 1: + try: + target = int(sys.argv[1]) + print( + f"Creating stratified dataset with target of {target:,} households..." + ) + output_file = create_stratified_cps_dataset( + target_households=target + ) + except ValueError: + print(f"Invalid target households: {sys.argv[1]}") + print("Usage: python create_stratified_cps.py [target_households]") + sys.exit(1) + else: + # Default target + print( + "Creating stratified dataset with default target of 30,000 households..." + ) + output_file = create_stratified_cps_dataset(target_households=30_000) + + print(f"\nDone! Created: {output_file}") + print("\nTo test loading:") + print(" from policyengine_us import Microsimulation") + print(f" sim = Microsimulation(dataset='{output_file}')") diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py b/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py new file mode 100644 index 00000000..ff648ee7 --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py @@ -0,0 +1,306 @@ +""" +Matrix tracer utility for debugging geo-stacking sparse matrices. + +This utility allows tracing through the complex stacked matrix structure +to verify values match simulation results. + +USAGE +===== + +Basic Setup: + + from matrix_tracer import MatrixTracer + + tracer = MatrixTracer( + targets_df, X_sparse, household_id_mapping, + cds_to_calibrate, sim + ) + +Common Operations: + + # 1. Understand what a column represents + col_info = tracer.get_column_info(100) + + # 2. Find where a household appears across all CDs + positions = tracer.get_household_column_positions(565) + + # 3. View matrix structure + tracer.print_matrix_structure() + +Matrix Structure: + + Columns are organized as: [CD1_households | CD2_households | ... | CD436_households] + Each CD block has n_households columns (e.g., 10,580 households) + + Formula to find column index: + column_idx = cd_block_number * n_households + household_index +""" + +import logging +import pandas as pd +import numpy as np +from typing import Dict, List +from scipy import sparse + + +logger = logging.getLogger(__name__) + + +class MatrixTracer: + """Trace through geo-stacked sparse matrices for debugging.""" + + def __init__( + self, + targets_df: pd.DataFrame, + matrix: sparse.csr_matrix, + household_id_mapping: Dict[str, List[str]], + geographic_ids: List[str], + sim, + ): + """ + Initialize tracer with matrix components. + + Args: + targets_df: DataFrame of all targets + matrix: The final stacked sparse matrix + household_id_mapping: Mapping from geo keys to household ID lists + geographic_ids: List of geographic IDs in order + sim: Microsimulation instance + """ + self.targets_df = targets_df + self.matrix = matrix + self.household_id_mapping = household_id_mapping + self.geographic_ids = geographic_ids + self.sim = sim + + # Get original household info + self.original_household_ids = sim.calculate("household_id").values + self.n_households = len(self.original_household_ids) + self.n_geographies = len(geographic_ids) + + # Build reverse lookup: original_hh_id -> index in original data + self.hh_id_to_index = { + hh_id: idx for idx, hh_id in enumerate(self.original_household_ids) + } + + # Build column catalog: maps column index -> (cd_geoid, household_id, household_index) + self.column_catalog = self._build_column_catalog() + + # Build row catalog: maps row index -> target info + self.row_catalog = self._build_row_catalog() + + logger.info( + f"Tracer initialized: {self.n_households} households x {self.n_geographies} geographies" + ) + logger.info(f"Matrix shape: {matrix.shape}") + + def _build_column_catalog(self) -> pd.DataFrame: + """Build a complete catalog of all matrix columns.""" + catalog = [] + col_idx = 0 + + for geo_id in self.geographic_ids: + for hh_idx, hh_id in enumerate(self.original_household_ids): + catalog.append( + { + "column_index": col_idx, + "cd_geoid": geo_id, + "household_id": hh_id, + "household_index": hh_idx, + } + ) + col_idx += 1 + + return pd.DataFrame(catalog) + + def _build_row_catalog(self) -> pd.DataFrame: + """Build a complete catalog of all matrix rows (targets).""" + catalog = [] + + for row_idx, (_, target) in enumerate(self.targets_df.iterrows()): + catalog.append( + { + "row_index": row_idx, + "variable": target["variable"], + "geographic_id": target.get("geographic_id", "unknown"), + "target_value": target["value"], + "stratum_id": target.get("stratum_id"), + "stratum_group_id": target.get("stratum_group_id", "unknown"), + } + ) + + return pd.DataFrame(catalog) + + def get_column_info(self, col_idx: int) -> Dict: + """Get information about a specific column.""" + if col_idx >= len(self.column_catalog): + raise ValueError( + f"Column index {col_idx} out of range (max: {len(self.column_catalog)-1})" + ) + return self.column_catalog.iloc[col_idx].to_dict() + + def get_row_info(self, row_idx: int) -> Dict: + """Get information about a specific row (target).""" + if row_idx >= len(self.row_catalog): + raise ValueError( + f"Row index {row_idx} out of range (max: {len(self.row_catalog)-1})" + ) + return self.row_catalog.iloc[row_idx].to_dict() + + def lookup_matrix_cell(self, row_idx: int, col_idx: int) -> Dict: + """ + Look up a specific matrix cell and return complete context. + + Args: + row_idx: Row index in matrix + col_idx: Column index in matrix + + Returns: + Dict with row info, column info, and matrix value + """ + row_info = self.get_row_info(row_idx) + col_info = self.get_column_info(col_idx) + matrix_value = self.matrix[row_idx, col_idx] + + return { + "row_index": row_idx, + "column_index": col_idx, + "matrix_value": float(matrix_value), + "target": row_info, + "household": col_info, + } + + def get_household_column_positions(self, original_hh_id: int) -> Dict[str, int]: + """ + Get all column positions for a household across all geographies. + + Args: + original_hh_id: Original household ID from simulation + + Returns: + Dict mapping geo_id to column position in stacked matrix + """ + if original_hh_id not in self.hh_id_to_index: + raise ValueError( + f"Household {original_hh_id} not found in original data" + ) + + # Get the household's index in the original data + hh_index = self.hh_id_to_index[original_hh_id] + + # Calculate column positions for each geography + positions = {} + for geo_idx, geo_id in enumerate(self.geographic_ids): + # Each geography gets a block of n_households columns + col_position = geo_idx * self.n_households + hh_index + positions[geo_id] = col_position + + return positions + + def print_matrix_structure(self): + """Print a comprehensive breakdown of the matrix structure.""" + print("\n" + "=" * 80) + print("MATRIX STRUCTURE BREAKDOWN") + print("=" * 80) + + print( + f"\nMatrix dimensions: {self.matrix.shape[0]} rows x {self.matrix.shape[1]} columns" + ) + print(f" Rows = {len(self.row_catalog)} targets") + print( + f" Columns = {self.n_households} households x {self.n_geographies} CDs" + ) + print( + f" = {self.n_households:,} x {self.n_geographies} = {self.matrix.shape[1]:,}" + ) + + print("\n" + "-" * 80) + print("COLUMN STRUCTURE (Households stacked by CD)") + print("-" * 80) + + # Build column ranges by CD + col_ranges = [] + cumulative = 0 + for geo_id in self.geographic_ids: + start_col = cumulative + end_col = cumulative + self.n_households - 1 + col_ranges.append( + { + "cd_geoid": geo_id, + "start_col": start_col, + "end_col": end_col, + "n_households": self.n_households, + } + ) + cumulative += self.n_households + + ranges_df = pd.DataFrame(col_ranges) + print(f"\nShowing first and last 5 CDs of {len(ranges_df)} total:") + print("\nFirst 5 CDs:") + print(ranges_df.head(5).to_string(index=False)) + print("\nLast 5 CDs:") + print(ranges_df.tail(5).to_string(index=False)) + + print("\n" + "-" * 80) + print("ROW STRUCTURE (Targets)") + print("-" * 80) + + print(f"\nTotal targets: {len(self.row_catalog)}") + print("\nTargets by stratum group:") + stratum_summary = ( + self.row_catalog.groupby("stratum_group_id") + .agg({"row_index": "count", "variable": lambda x: len(set(x))}) + .rename(columns={"row_index": "n_targets", "variable": "n_unique_vars"}) + ) + print(stratum_summary.to_string()) + + print("\n" + "=" * 80) + + def print_column_catalog(self, max_rows: int = 50): + """Print a sample of the column catalog.""" + print( + f"\nColumn Catalog (showing first {max_rows} of {len(self.column_catalog)}):" + ) + print(self.column_catalog.head(max_rows).to_string(index=False)) + + def print_row_catalog(self, max_rows: int = 50): + """Print a sample of the row catalog.""" + print( + f"\nRow Catalog (showing first {max_rows} of {len(self.row_catalog)}):" + ) + print(self.row_catalog.head(max_rows).to_string(index=False)) + + def trace_household_targets(self, original_hh_id: int) -> pd.DataFrame: + """ + Extract all target values for a household across all geographies. + + Args: + original_hh_id: Original household ID to trace + + Returns: + DataFrame with target details and values for this household + """ + positions = self.get_household_column_positions(original_hh_id) + + results = [] + + for target_idx, (_, target) in enumerate(self.targets_df.iterrows()): + target_result = { + "target_idx": target_idx, + "variable": target["variable"], + "target_value": target["value"], + "geographic_id": target.get("geographic_id", "unknown"), + "stratum_group_id": target.get("stratum_group_id", "unknown"), + } + + # Extract values for this target across all geographies + for geo_id, col_pos in positions.items(): + if col_pos < self.matrix.shape[1]: + matrix_value = self.matrix[target_idx, col_pos] + target_result[f"matrix_value_{geo_id}"] = matrix_value + else: + target_result[f"matrix_value_{geo_id}"] = np.nan + + results.append(target_result) + + return pd.DataFrame(results) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py new file mode 100644 index 00000000..4aa28b84 --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py @@ -0,0 +1,186 @@ +""" +Sparse matrix builder for geo-stacking calibration. + +Generic, database-driven approach where all constraints (including geographic) +are evaluated as masks. Geographic constraints work because we SET state_fips +before evaluating constraints. +""" + +from collections import defaultdict +from typing import Dict, List, Optional, Tuple +import numpy as np +import pandas as pd +from scipy import sparse +from sqlalchemy import create_engine, text + +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_calculated_variables, + apply_op, + _get_geo_level, +) + + +class SparseMatrixBuilder: + """Build sparse calibration matrices for geo-stacking.""" + + def __init__(self, db_uri: str, time_period: int, cds_to_calibrate: List[str], + dataset_path: Optional[str] = None): + self.db_uri = db_uri + self.engine = create_engine(db_uri) + self.time_period = time_period + self.cds_to_calibrate = cds_to_calibrate + self.dataset_path = dataset_path + + def _query_targets(self, target_filter: dict) -> pd.DataFrame: + """Query targets based on filter criteria using OR logic.""" + or_conditions = [] + + if "stratum_group_ids" in target_filter: + ids = ",".join(map(str, target_filter["stratum_group_ids"])) + or_conditions.append(f"s.stratum_group_id IN ({ids})") + + if "variables" in target_filter: + vars_str = ",".join(f"'{v}'" for v in target_filter["variables"]) + or_conditions.append(f"t.variable IN ({vars_str})") + + if "target_ids" in target_filter: + ids = ",".join(map(str, target_filter["target_ids"])) + or_conditions.append(f"t.target_id IN ({ids})") + + if "stratum_ids" in target_filter: + ids = ",".join(map(str, target_filter["stratum_ids"])) + or_conditions.append(f"t.stratum_id IN ({ids})") + + if not or_conditions: + raise ValueError("target_filter must specify at least one filter criterion") + + where_clause = " OR ".join(f"({c})" for c in or_conditions) + + query = f""" + SELECT t.target_id, t.stratum_id, t.variable, t.value, t.period, + s.stratum_group_id + FROM targets t + JOIN strata s ON t.stratum_id = s.stratum_id + WHERE {where_clause} + ORDER BY t.target_id + """ + + with self.engine.connect() as conn: + return pd.read_sql(query, conn) + + def _get_constraints(self, stratum_id: int) -> List[dict]: + """Get all constraints for a stratum (including geographic).""" + query = """ + SELECT constraint_variable as variable, operation, value + FROM stratum_constraints + WHERE stratum_id = :stratum_id + """ + with self.engine.connect() as conn: + df = pd.read_sql(query, conn, params={"stratum_id": stratum_id}) + return df.to_dict('records') + + def _get_geographic_id(self, stratum_id: int) -> str: + """Extract geographic_id from constraints for targets_df.""" + constraints = self._get_constraints(stratum_id) + for c in constraints: + if c['variable'] == 'state_fips': + return c['value'] + if c['variable'] == 'congressional_district_geoid': + return c['value'] + return 'US' + + def _create_state_sim(self, state: int, n_households: int): + """Create a fresh simulation with state_fips set to given state.""" + from policyengine_us import Microsimulation + state_sim = Microsimulation(dataset=self.dataset_path) + state_sim.set_input("state_fips", self.time_period, + np.full(n_households, state, dtype=np.int32)) + for var in get_calculated_variables(state_sim): + state_sim.delete_arrays(var) + return state_sim + + def build_matrix(self, sim, target_filter: dict) -> Tuple[pd.DataFrame, sparse.csr_matrix, Dict[str, List[str]]]: + """ + Build sparse calibration matrix. + + Args: + sim: Microsimulation instance (used for household_ids, or as template) + target_filter: Dict specifying which targets to include + - {"stratum_group_ids": [4]} for SNAP targets + - {"target_ids": [123, 456]} for specific targets + + Returns: + Tuple of (targets_df, X_sparse, household_id_mapping) + """ + household_ids = sim.calculate("household_id", map_to="household").values + n_households = len(household_ids) + n_cds = len(self.cds_to_calibrate) + n_cols = n_households * n_cds + + targets_df = self._query_targets(target_filter) + n_targets = len(targets_df) + + if n_targets == 0: + raise ValueError("No targets found matching filter") + + targets_df['geographic_id'] = targets_df['stratum_id'].apply(self._get_geographic_id) + + # Sort by (geo_level, variable, geographic_id) for contiguous group rows + targets_df['_geo_level'] = targets_df['geographic_id'].apply(_get_geo_level) + targets_df = targets_df.sort_values(['_geo_level', 'variable', 'geographic_id']) + targets_df = targets_df.drop(columns=['_geo_level']).reset_index(drop=True) + + X = sparse.lil_matrix((n_targets, n_cols), dtype=np.float32) + + cds_by_state = defaultdict(list) + for cd_idx, cd in enumerate(self.cds_to_calibrate): + state = int(cd) // 100 + cds_by_state[state].append((cd_idx, cd)) + + for state, cd_list in cds_by_state.items(): + if self.dataset_path: + state_sim = self._create_state_sim(state, n_households) + else: + state_sim = sim + state_sim.set_input("state_fips", self.time_period, + np.full(n_households, state, dtype=np.int32)) + for var in get_calculated_variables(state_sim): + state_sim.delete_arrays(var) + + for cd_idx, cd in cd_list: + col_start = cd_idx * n_households + + for row_idx, (_, target) in enumerate(targets_df.iterrows()): + constraints = self._get_constraints(target['stratum_id']) + + mask = np.ones(n_households, dtype=bool) + for c in constraints: + if c['variable'] == 'congressional_district_geoid': + if c['operation'] in ('==', '=') and c['value'] != cd: + mask[:] = False + elif c['variable'] == 'state_fips': + if c['operation'] in ('==', '=') and int(c['value']) != state: + mask[:] = False + else: + try: + values = state_sim.calculate(c['variable'], map_to='household').values + mask &= apply_op(values, c['operation'], c['value']) + except Exception: + pass + + if not mask.any(): + continue + + target_values = state_sim.calculate(target['variable'], map_to='household').values + masked_values = (target_values * mask).astype(np.float32) + + nonzero = np.where(masked_values != 0)[0] + if len(nonzero) > 0: + X[row_idx, col_start + nonzero] = masked_values[nonzero] + + household_id_mapping = {} + for cd in self.cds_to_calibrate: + key = f"cd{cd}" + household_id_mapping[key] = [f"{hh_id}_{key}" for hh_id in household_ids] + + return targets_df, X.tocsr(), household_id_mapping diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py new file mode 100644 index 00000000..dd1fbec3 --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py @@ -0,0 +1,542 @@ +""" +Verification tests for the sparse matrix builder. + +RATIONALE +========= +The sparse matrix X_sparse contains pre-calculated values for households +"transplanted" to different congressional districts. When a household moves +to a CD in a different state, state-dependent benefits like SNAP are +recalculated under the destination state's rules. + +This creates a verification challenge: we can't easily verify that SNAP +*should* be $11,560 in NC vs $14,292 in AK without reimplementing the +entire SNAP formula. However, we CAN verify: + +1. CONSISTENCY: X_sparse values match an independently-created simulation + with state_fips set to the destination state. This confirms the sparse + matrix builder correctly uses PolicyEngine's calculation engine. + +2. SAME-STATE INVARIANCE: When a household's original state equals the + destination CD's state, the value should exactly match the original + simulation. Any mismatch here is definitively a bug (not a policy difference). + +3. GEOGRAPHIC MASKING: Zero cells should be zero because of geographic + constraint mismatches: + - State-level targets: only CDs in that state have non-zero values + - CD-level targets: only that specific CD has non-zero values (even + same-state different-CD columns should be zero) + - National targets: NO geographic masking - all CD columns can have + non-zero values, but values DIFFER by destination state because + benefits are recalculated under each state's rules + +By verifying these properties, we confirm the sparse matrix builder is +working correctly without needing to understand every state-specific +policy formula. + +CACHE CLEARING LESSON +===================== +When setting state_fips via set_input(), you MUST clear cached calculated +variables to force recalculation. Use get_calculated_variables() which +returns variables with formulas - these are the ones that need recalculation. + +DO NOT use `var not in sim.input_variables` - this misses variables that +are BOTH inputs AND have formulas (12 such variables exist). If any of +these are in the dependency chain, the recalculation will use stale values. + +Correct pattern: + sim.set_input("state_fips", period, new_values) + for var in get_calculated_variables(sim): + sim.delete_arrays(var) + +USAGE +===== +Run interactively or with pytest: + + python test_sparse_matrix_builder.py + pytest test_sparse_matrix_builder.py -v +""" + +import numpy as np +import pandas as pd +from typing import List + +from policyengine_us import Microsimulation +from policyengine_us_data.datasets.cps.local_area_calibration.sparse_matrix_builder import SparseMatrixBuilder +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import get_calculated_variables + + +def test_column_indexing(X_sparse, tracer, test_cds) -> bool: + """ + Test 1: Verify column indexing roundtrip. + + Column index = cd_idx * n_households + household_index + This is pure math - if this fails, everything else is unreliable. + """ + n_hh = tracer.n_households + hh_ids = tracer.original_household_ids + errors = [] + + test_cases = [] + for cd_idx in [0, len(test_cds)//2, len(test_cds)-1]: + for hh_idx in [0, 100, n_hh-1]: + test_cases.append((cd_idx, hh_idx)) + + for cd_idx, hh_idx in test_cases: + cd = test_cds[cd_idx] + hh_id = hh_ids[hh_idx] + expected_col = cd_idx * n_hh + hh_idx + col_info = tracer.get_column_info(expected_col) + positions = tracer.get_household_column_positions(hh_id) + pos_col = positions[cd] + + if col_info['cd_geoid'] != cd: + errors.append(f"CD mismatch at col {expected_col}") + if col_info['household_index'] != hh_idx: + errors.append(f"HH index mismatch at col {expected_col}") + if col_info['household_id'] != hh_id: + errors.append(f"HH ID mismatch at col {expected_col}") + if pos_col != expected_col: + errors.append(f"Position mismatch for hh {hh_id}, cd {cd}") + + expected_cols = len(test_cds) * n_hh + if X_sparse.shape[1] != expected_cols: + errors.append(f"Matrix width mismatch: expected {expected_cols}, got {X_sparse.shape[1]}") + + if errors: + print("X Column indexing FAILED:") + for e in errors: + print(f" {e}") + return False + + print(f"[PASS] Column indexing: {len(test_cases)} cases, {len(test_cds)} CDs x {n_hh} households") + return True + + +def test_same_state_matches_original(X_sparse, targets_df, tracer, sim, test_cds, + dataset_path, n_samples=200, seed=42) -> bool: + """ + Test 2: Same-state non-zero cells must match fresh same-state simulation. + + When household stays in same state, X_sparse should contain the value + calculated from a fresh simulation with state_fips set to that state + (same as the matrix builder does). + """ + rng = np.random.default_rng(seed) + n_hh = tracer.n_households + hh_ids = tracer.original_household_ids + hh_states = sim.calculate("state_fips", map_to="household").values + + state_sims = {} + def get_state_sim(state): + if state not in state_sims: + s = Microsimulation(dataset=dataset_path) + s.set_input("state_fips", 2023, np.full(n_hh, state, dtype=np.int32)) + for var in get_calculated_variables(s): + s.delete_arrays(var) + state_sims[state] = s + return state_sims[state] + + nonzero_rows, nonzero_cols = X_sparse.nonzero() + + same_state_indices = [] + for i in range(len(nonzero_rows)): + col_idx = nonzero_cols[i] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + orig_state = int(hh_states[hh_idx]) + if dest_state == orig_state: + same_state_indices.append(i) + + if not same_state_indices: + print("[WARN] No same-state non-zero cells found") + return True + + sample_idx = rng.choice(same_state_indices, min(n_samples, len(same_state_indices)), replace=False) + errors = [] + + for idx in sample_idx: + row_idx = nonzero_rows[idx] + col_idx = nonzero_cols[idx] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + variable = targets_df.iloc[row_idx]['variable'] + actual = float(X_sparse[row_idx, col_idx]) + state_sim = get_state_sim(dest_state) + expected = float(state_sim.calculate(variable, map_to='household').values[hh_idx]) + + if not np.isclose(actual, expected, atol=0.5): + errors.append({ + 'hh_id': hh_ids[hh_idx], + 'variable': variable, + 'actual': actual, + 'expected': expected + }) + + if errors: + print(f"X Same-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches") + for e in errors[:5]: + print(f" hh={e['hh_id']}, var={e['variable']}: {e['actual']:.2f} vs {e['expected']:.2f}") + return False + + print(f"[PASS] Same-state: {len(sample_idx)}/{len(sample_idx)} match fresh same-state simulation") + return True + + +def test_cross_state_matches_swapped_sim(X_sparse, targets_df, tracer, test_cds, + dataset_path, n_samples=200, seed=42) -> bool: + """ + Test 3: Cross-state non-zero cells must match state-swapped simulation. + + When household moves to different state, X_sparse should contain the + value calculated from a fresh simulation with state_fips set to destination state. + """ + rng = np.random.default_rng(seed) + sim_orig = Microsimulation(dataset=dataset_path) + n_hh = tracer.n_households + hh_ids = tracer.original_household_ids + hh_states = sim_orig.calculate("state_fips", map_to="household").values + + state_sims = {} + def get_state_sim(state): + if state not in state_sims: + s = Microsimulation(dataset=dataset_path) + s.set_input("state_fips", 2023, np.full(n_hh, state, dtype=np.int32)) + for var in get_calculated_variables(s): + s.delete_arrays(var) + state_sims[state] = s + return state_sims[state] + + nonzero_rows, nonzero_cols = X_sparse.nonzero() + + cross_state_indices = [] + for i in range(len(nonzero_rows)): + col_idx = nonzero_cols[i] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + orig_state = int(hh_states[hh_idx]) + if dest_state != orig_state: + cross_state_indices.append(i) + + if not cross_state_indices: + print("[WARN] No cross-state non-zero cells found") + return True + + sample_idx = rng.choice(cross_state_indices, min(n_samples, len(cross_state_indices)), replace=False) + errors = [] + + for idx in sample_idx: + row_idx = nonzero_rows[idx] + col_idx = nonzero_cols[idx] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + variable = targets_df.iloc[row_idx]['variable'] + actual = float(X_sparse[row_idx, col_idx]) + state_sim = get_state_sim(dest_state) + expected = float(state_sim.calculate(variable, map_to='household').values[hh_idx]) + + if not np.isclose(actual, expected, atol=0.5): + errors.append({ + 'hh_id': hh_ids[hh_idx], + 'orig_state': int(hh_states[hh_idx]), + 'dest_state': dest_state, + 'variable': variable, + 'actual': actual, + 'expected': expected + }) + + if errors: + print(f"X Cross-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches") + for e in errors[:5]: + print(f" hh={e['hh_id']}, {e['orig_state']}->{e['dest_state']}: {e['actual']:.2f} vs {e['expected']:.2f}") + return False + + print(f"[PASS] Cross-state: {len(sample_idx)}/{len(sample_idx)} match state-swapped simulation") + return True + + +def test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds, + n_samples=100, seed=42) -> bool: + """ + Test 4: State-level targets have zeros for wrong-state CD columns. + + For a target with geographic_id=37 (NC), columns for CDs in other states + (HI, MT, AK) should all be zero. + """ + rng = np.random.default_rng(seed) + n_hh = tracer.n_households + + state_targets = [] + for row_idx in range(len(targets_df)): + geo_id = targets_df.iloc[row_idx].get('geographic_id', 'US') + if geo_id != 'US': + try: + val = int(geo_id) + if val < 100: + state_targets.append((row_idx, val)) + except (ValueError, TypeError): + pass + + if not state_targets: + print("[WARN] No state-level targets found") + return True + + errors = [] + checked = 0 + sample_targets = rng.choice(len(state_targets), min(20, len(state_targets)), replace=False) + + for idx in sample_targets: + row_idx, target_state = state_targets[idx] + other_state_cds = [(i, cd) for i, cd in enumerate(test_cds) + if int(cd) // 100 != target_state] + if not other_state_cds: + continue + + sample_cds = rng.choice(len(other_state_cds), min(5, len(other_state_cds)), replace=False) + for cd_sample_idx in sample_cds: + cd_idx, cd = other_state_cds[cd_sample_idx] + sample_hh = rng.choice(n_hh, min(5, n_hh), replace=False) + for hh_idx in sample_hh: + col_idx = cd_idx * n_hh + hh_idx + actual = X_sparse[row_idx, col_idx] + checked += 1 + if actual != 0: + errors.append({'row': row_idx, 'cd': cd, 'value': float(actual)}) + + if errors: + print(f"X State-level masking FAILED: {len(errors)}/{checked} should be zero") + return False + + print(f"[PASS] State-level masking: {checked}/{checked} wrong-state cells are zero") + return True + + +def test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds, seed=42) -> bool: + """ + Test 5: CD-level targets have zeros for other CDs, even same-state. + + For a target with geographic_id=3707, columns for CDs 3701-3706, 3708-3714 + should all be zero, even though they're all in NC (state 37). + + Note: Requires test_cds to include multiple CDs from the same state as + some CD-level target geographic_ids. + """ + rng = np.random.default_rng(seed) + n_hh = tracer.n_households + + cd_targets_with_same_state = [] + for row_idx in range(len(targets_df)): + geo_id = targets_df.iloc[row_idx].get('geographic_id', 'US') + if geo_id != 'US': + try: + val = int(geo_id) + if val >= 100: + target_state = val // 100 + same_state_other_cds = [cd for cd in test_cds + if int(cd) // 100 == target_state and cd != geo_id] + if same_state_other_cds: + cd_targets_with_same_state.append((row_idx, geo_id, same_state_other_cds)) + except (ValueError, TypeError): + pass + + if not cd_targets_with_same_state: + print("[WARN] No CD-level targets with same-state other CDs in test_cds") + return True + + errors = [] + same_state_checks = 0 + + for row_idx, target_cd, other_cds in cd_targets_with_same_state[:10]: + for cd in other_cds: + cd_idx = test_cds.index(cd) + for hh_idx in rng.choice(n_hh, 3, replace=False): + col_idx = cd_idx * n_hh + hh_idx + actual = X_sparse[row_idx, col_idx] + same_state_checks += 1 + if actual != 0: + errors.append({'target_cd': target_cd, 'other_cd': cd, 'value': float(actual)}) + + if errors: + print(f"X CD-level masking FAILED: {len(errors)} same-state-different-CD non-zero values") + for e in errors[:5]: + print(f" target={e['target_cd']}, other={e['other_cd']}, value={e['value']}") + return False + + print(f"[PASS] CD-level masking: {same_state_checks} same-state-different-CD checks, all zero") + return True + + +def test_national_no_geo_masking(X_sparse, targets_df, tracer, sim, test_cds, + dataset_path, seed=42) -> bool: + """ + Test 6: National targets have no geographic masking. + + National targets (geographic_id='US') can have non-zero values for ANY CD. + Moreover, values DIFFER by destination state because benefits are + recalculated under each state's rules. + + Example: Household 177332 (originally AK with SNAP=$14,292) + - X_sparse[national_row, AK_CD_col] = $14,292 (staying in AK) + - X_sparse[national_row, NC_CD_col] = $11,560 (recalculated for NC) + + We verify by: + 1. Finding households with non-zero values in the national target + 2. Checking they have values in multiple states' CD columns + 3. Confirming values differ between states (due to recalculation) + """ + rng = np.random.default_rng(seed) + n_hh = tracer.n_households + hh_ids = tracer.original_household_ids + + national_rows = [i for i in range(len(targets_df)) + if targets_df.iloc[i].get('geographic_id', 'US') == 'US'] + + if not national_rows: + print("[WARN] No national targets found") + return True + + states_in_test = sorted(set(int(cd) // 100 for cd in test_cds)) + cds_by_state = {state: [cd for cd in test_cds if int(cd) // 100 == state] + for state in states_in_test} + + print(f" States in test: {states_in_test}") + + for row_idx in national_rows: + variable = targets_df.iloc[row_idx]['variable'] + + # Find households with non-zero values in this national target + row_data = X_sparse.getrow(row_idx) + nonzero_cols = row_data.nonzero()[1] + + if len(nonzero_cols) == 0: + print(f"X National target row {row_idx} ({variable}) has no non-zero values!") + return False + + # Pick a few households that have non-zero values + sample_cols = rng.choice(nonzero_cols, min(5, len(nonzero_cols)), replace=False) + + households_checked = 0 + households_with_multi_state_values = 0 + + for col_idx in sample_cols: + hh_idx = col_idx % n_hh + hh_id = hh_ids[hh_idx] + + # Get this household's values across different states + values_by_state = {} + for state, cds in cds_by_state.items(): + cd = cds[0] # Just check first CD in each state + cd_idx = test_cds.index(cd) + state_col = cd_idx * n_hh + hh_idx + val = float(X_sparse[row_idx, state_col]) + if val != 0: + values_by_state[state] = val + + households_checked += 1 + if len(values_by_state) > 1: + households_with_multi_state_values += 1 + + print(f" Row {row_idx} ({variable}): {households_with_multi_state_values}/{households_checked} " + f"households have values in multiple states") + + print(f"[PASS] National targets: no geographic masking, values vary by destination state") + return True + + +def run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) -> bool: + """Run all verification tests and return overall pass/fail.""" + print("=" * 70) + print("SPARSE MATRIX VERIFICATION TESTS") + print("=" * 70) + + results = [] + + print("\n[Test 1] Column Indexing") + results.append(test_column_indexing(X_sparse, tracer, test_cds)) + + print("\n[Test 2] Same-State Values Match Fresh Sim") + results.append(test_same_state_matches_original(X_sparse, targets_df, tracer, sim, test_cds, dataset_path)) + + print("\n[Test 3] Cross-State Values Match State-Swapped Sim") + results.append(test_cross_state_matches_swapped_sim(X_sparse, targets_df, tracer, test_cds, dataset_path)) + + print("\n[Test 4] State-Level Zero Masking") + results.append(test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds)) + + print("\n[Test 5] CD-Level Zero Masking (Same-State-Different-CD)") + results.append(test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds)) + + print("\n[Test 6] National Targets No Geo Masking") + results.append(test_national_no_geo_masking(X_sparse, targets_df, tracer, sim, test_cds, dataset_path)) + + print("\n" + "=" * 70) + passed = sum(results) + total = len(results) + if passed == total: + print(f"ALL TESTS PASSED ({passed}/{total})") + else: + print(f"SOME TESTS FAILED ({passed}/{total} passed)") + print("=" * 70) + + return all(results) + + +if __name__ == "__main__": + from sqlalchemy import create_engine, text + from policyengine_us_data.storage import STORAGE_FOLDER + from policyengine_us_data.datasets.cps.local_area_calibration.matrix_tracer import MatrixTracer + + print("Setting up verification tests...") + + db_path = STORAGE_FOLDER / "policy_data.db" + db_uri = f"sqlite:///{db_path}" + dataset_path = str(STORAGE_FOLDER / "stratified_extended_cps_2023.h5") + + # Test with NC, HI, MT, AK CDs (manageable size, includes same-state CDs for Test 5) + engine = create_engine(db_uri) + query = """ + SELECT DISTINCT sc.value as cd_geoid + FROM strata s + JOIN stratum_constraints sc ON s.stratum_id = sc.stratum_id + WHERE s.stratum_group_id = 1 + AND sc.constraint_variable = 'congressional_district_geoid' + AND ( + sc.value LIKE '37__' + OR sc.value LIKE '150_' + OR sc.value LIKE '300_' + OR sc.value = '200' OR sc.value = '201' + ) + ORDER BY sc.value + """ + with engine.connect() as conn: + result = conn.execute(text(query)).fetchall() + test_cds = [row[0] for row in result] + + print(f"Testing with {len(test_cds)} CDs from 4 states") + + sim = Microsimulation(dataset=dataset_path) + builder = SparseMatrixBuilder( + db_uri, time_period=2023, + cds_to_calibrate=test_cds, + dataset_path=dataset_path + ) + + print("Building sparse matrix...") + targets_df, X_sparse, household_id_mapping = builder.build_matrix( + sim, + target_filter={"stratum_group_ids": [4], "variables": ["snap"]} + ) + + tracer = MatrixTracer(targets_df, X_sparse, household_id_mapping, test_cds, sim) + + print(f"Matrix shape: {X_sparse.shape}, non-zero: {X_sparse.nnz}\n") + + success = run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) + exit(0 if success else 1) diff --git a/policyengine_us_data/datasets/puf/puf.py b/policyengine_us_data/datasets/puf/puf.py index cac9ad61..99d93e4a 100644 --- a/policyengine_us_data/datasets/puf/puf.py +++ b/policyengine_us_data/datasets/puf/puf.py @@ -732,6 +732,13 @@ class PUF_2021(PUF): url = "release://policyengine/irs-soi-puf/1.8.0/puf_2021.h5" +class PUF_2023(PUF): + label = "PUF 2023" + name = "puf_2023" + time_period = 2023 + file_path = STORAGE_FOLDER / "puf_2023.h5" + + class PUF_2024(PUF): label = "PUF 2024 (2015-based)" name = "puf_2024" @@ -748,6 +755,12 @@ class PUF_2024(PUF): } if __name__ == "__main__": - PUF_2015().generate() - PUF_2021().generate() - PUF_2024().generate() + import os + geo_stacking = os.environ.get("GEO_STACKING") == "true" + + if geo_stacking: + PUF_2023().generate() + else: + PUF_2015().generate() + PUF_2021().generate() + PUF_2024().generate() diff --git a/policyengine_us_data/storage/download_private_prerequisites.py b/policyengine_us_data/storage/download_private_prerequisites.py index 26696d6c..3e080274 100644 --- a/policyengine_us_data/storage/download_private_prerequisites.py +++ b/policyengine_us_data/storage/download_private_prerequisites.py @@ -27,3 +27,9 @@ local_folder=FOLDER, version=None, ) +download( + repo="policyengine/policyengine-us-data", + repo_filename="policy_data.db", + local_folder=FOLDER, + version=None, +) From 04000663ae1e82c6df7373e43b8dc0603745cf92 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 5 Dec 2025 11:41:29 -0500 Subject: [PATCH 02/24] Add changelog entry and format code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 8 + .../calibration_utils.py | 41 ++- .../create_stratified_cps.py | 5 +- .../local_area_calibration/matrix_tracer.py | 12 +- .../sparse_matrix_builder.py | 100 ++++-- .../test_sparse_matrix_builder.py | 306 +++++++++++++----- policyengine_us_data/datasets/puf/puf.py | 1 + 7 files changed, 338 insertions(+), 135 deletions(-) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29b..b6da5347 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,8 @@ +- bump: minor + changes: + added: + - Sparse matrix builder for local area calibration with database-driven constraints + - Geo-stacking data pipeline (make data-geo) for congressional district calibration + - ExtendedCPS_2023, PUF_2023, CPS_2024_Full dataset classes + - Stratified CPS sampling to preserve high-income households + - Matrix verification tests for geo-stacking calibration diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index cce627e2..663d5147 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -20,11 +20,24 @@ def get_calculated_variables(sim) -> List[str]: If IDs change, random-dependent variables (SSI resource test, WIC nutritional risk, WIC takeup) produce different results. """ - exclude_ids = {'person_id', 'household_id', 'tax_unit_id', 'spm_unit_id', - 'family_id', 'marital_unit_id'} - return [name for name, var in sim.tax_benefit_system.variables.items() - if (var.formulas or getattr(var, 'adds', None) or getattr(var, 'subtracts', None)) - and name not in exclude_ids] + exclude_ids = { + "person_id", + "household_id", + "tax_unit_id", + "spm_unit_id", + "family_id", + "marital_unit_id", + } + return [ + name + for name, var in sim.tax_benefit_system.variables.items() + if ( + var.formulas + or getattr(var, "adds", None) + or getattr(var, "subtracts", None) + ) + and name not in exclude_ids + ] def apply_op(values: np.ndarray, op: str, val: str) -> np.ndarray: @@ -34,31 +47,31 @@ def apply_op(values: np.ndarray, op: str, val: str) -> np.ndarray: if parsed.is_integer(): parsed = int(parsed) except ValueError: - if val == 'True': + if val == "True": parsed = True - elif val == 'False': + elif val == "False": parsed = False else: parsed = val - if op in ('==', '='): + if op in ("==", "="): return values == parsed - if op == '>': + if op == ">": return values > parsed - if op == '>=': + if op == ">=": return values >= parsed - if op == '<': + if op == "<": return values < parsed - if op == '<=': + if op == "<=": return values <= parsed - if op == '!=': + if op == "!=": return values != parsed return np.ones(len(values), dtype=bool) def _get_geo_level(geo_id) -> int: """Return geographic level: 0=National, 1=State, 2=District.""" - if geo_id == 'US': + if geo_id == "US": return 0 try: val = int(geo_id) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py index d1066060..cb85d082 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py @@ -187,6 +187,7 @@ def create_stratified_cps_dataset( # Generate output path if not provided if output_path is None: from policyengine_us_data.storage import STORAGE_FOLDER + output_path = str(STORAGE_FOLDER / "stratified_extended_cps_2023.h5") # Save to h5 file @@ -195,7 +196,9 @@ def create_stratified_cps_dataset( # Only save input variables (not calculated/derived variables) input_vars = set(stratified_sim.input_variables) - print(f"Found {len(input_vars)} input variables (excluding calculated variables)") + print( + f"Found {len(input_vars)} input variables (excluding calculated variables)" + ) for variable in stratified_sim.tax_benefit_system.variables: if variable not in input_vars: diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py b/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py index ff648ee7..8031422a 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py @@ -125,7 +125,9 @@ def _build_row_catalog(self) -> pd.DataFrame: "geographic_id": target.get("geographic_id", "unknown"), "target_value": target["value"], "stratum_id": target.get("stratum_id"), - "stratum_group_id": target.get("stratum_group_id", "unknown"), + "stratum_group_id": target.get( + "stratum_group_id", "unknown" + ), } ) @@ -170,7 +172,9 @@ def lookup_matrix_cell(self, row_idx: int, col_idx: int) -> Dict: "household": col_info, } - def get_household_column_positions(self, original_hh_id: int) -> Dict[str, int]: + def get_household_column_positions( + self, original_hh_id: int + ) -> Dict[str, int]: """ Get all column positions for a household across all geographies. @@ -250,7 +254,9 @@ def print_matrix_structure(self): stratum_summary = ( self.row_catalog.groupby("stratum_group_id") .agg({"row_index": "count", "variable": lambda x: len(set(x))}) - .rename(columns={"row_index": "n_targets", "variable": "n_unique_vars"}) + .rename( + columns={"row_index": "n_targets", "variable": "n_unique_vars"} + ) ) print(stratum_summary.to_string()) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py index 4aa28b84..568ebca9 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/sparse_matrix_builder.py @@ -23,8 +23,13 @@ class SparseMatrixBuilder: """Build sparse calibration matrices for geo-stacking.""" - def __init__(self, db_uri: str, time_period: int, cds_to_calibrate: List[str], - dataset_path: Optional[str] = None): + def __init__( + self, + db_uri: str, + time_period: int, + cds_to_calibrate: List[str], + dataset_path: Optional[str] = None, + ): self.db_uri = db_uri self.engine = create_engine(db_uri) self.time_period = time_period @@ -52,7 +57,9 @@ def _query_targets(self, target_filter: dict) -> pd.DataFrame: or_conditions.append(f"t.stratum_id IN ({ids})") if not or_conditions: - raise ValueError("target_filter must specify at least one filter criterion") + raise ValueError( + "target_filter must specify at least one filter criterion" + ) where_clause = " OR ".join(f"({c})" for c in or_conditions) @@ -77,29 +84,35 @@ def _get_constraints(self, stratum_id: int) -> List[dict]: """ with self.engine.connect() as conn: df = pd.read_sql(query, conn, params={"stratum_id": stratum_id}) - return df.to_dict('records') + return df.to_dict("records") def _get_geographic_id(self, stratum_id: int) -> str: """Extract geographic_id from constraints for targets_df.""" constraints = self._get_constraints(stratum_id) for c in constraints: - if c['variable'] == 'state_fips': - return c['value'] - if c['variable'] == 'congressional_district_geoid': - return c['value'] - return 'US' + if c["variable"] == "state_fips": + return c["value"] + if c["variable"] == "congressional_district_geoid": + return c["value"] + return "US" def _create_state_sim(self, state: int, n_households: int): """Create a fresh simulation with state_fips set to given state.""" from policyengine_us import Microsimulation + state_sim = Microsimulation(dataset=self.dataset_path) - state_sim.set_input("state_fips", self.time_period, - np.full(n_households, state, dtype=np.int32)) + state_sim.set_input( + "state_fips", + self.time_period, + np.full(n_households, state, dtype=np.int32), + ) for var in get_calculated_variables(state_sim): state_sim.delete_arrays(var) return state_sim - def build_matrix(self, sim, target_filter: dict) -> Tuple[pd.DataFrame, sparse.csr_matrix, Dict[str, List[str]]]: + def build_matrix( + self, sim, target_filter: dict + ) -> Tuple[pd.DataFrame, sparse.csr_matrix, Dict[str, List[str]]]: """ Build sparse calibration matrix. @@ -112,7 +125,9 @@ def build_matrix(self, sim, target_filter: dict) -> Tuple[pd.DataFrame, sparse.c Returns: Tuple of (targets_df, X_sparse, household_id_mapping) """ - household_ids = sim.calculate("household_id", map_to="household").values + household_ids = sim.calculate( + "household_id", map_to="household" + ).values n_households = len(household_ids) n_cds = len(self.cds_to_calibrate) n_cols = n_households * n_cds @@ -123,12 +138,20 @@ def build_matrix(self, sim, target_filter: dict) -> Tuple[pd.DataFrame, sparse.c if n_targets == 0: raise ValueError("No targets found matching filter") - targets_df['geographic_id'] = targets_df['stratum_id'].apply(self._get_geographic_id) + targets_df["geographic_id"] = targets_df["stratum_id"].apply( + self._get_geographic_id + ) # Sort by (geo_level, variable, geographic_id) for contiguous group rows - targets_df['_geo_level'] = targets_df['geographic_id'].apply(_get_geo_level) - targets_df = targets_df.sort_values(['_geo_level', 'variable', 'geographic_id']) - targets_df = targets_df.drop(columns=['_geo_level']).reset_index(drop=True) + targets_df["_geo_level"] = targets_df["geographic_id"].apply( + _get_geo_level + ) + targets_df = targets_df.sort_values( + ["_geo_level", "variable", "geographic_id"] + ) + targets_df = targets_df.drop(columns=["_geo_level"]).reset_index( + drop=True + ) X = sparse.lil_matrix((n_targets, n_cols), dtype=np.float32) @@ -142,8 +165,11 @@ def build_matrix(self, sim, target_filter: dict) -> Tuple[pd.DataFrame, sparse.c state_sim = self._create_state_sim(state, n_households) else: state_sim = sim - state_sim.set_input("state_fips", self.time_period, - np.full(n_households, state, dtype=np.int32)) + state_sim.set_input( + "state_fips", + self.time_period, + np.full(n_households, state, dtype=np.int32), + ) for var in get_calculated_variables(state_sim): state_sim.delete_arrays(var) @@ -151,36 +177,52 @@ def build_matrix(self, sim, target_filter: dict) -> Tuple[pd.DataFrame, sparse.c col_start = cd_idx * n_households for row_idx, (_, target) in enumerate(targets_df.iterrows()): - constraints = self._get_constraints(target['stratum_id']) + constraints = self._get_constraints(target["stratum_id"]) mask = np.ones(n_households, dtype=bool) for c in constraints: - if c['variable'] == 'congressional_district_geoid': - if c['operation'] in ('==', '=') and c['value'] != cd: + if c["variable"] == "congressional_district_geoid": + if ( + c["operation"] in ("==", "=") + and c["value"] != cd + ): mask[:] = False - elif c['variable'] == 'state_fips': - if c['operation'] in ('==', '=') and int(c['value']) != state: + elif c["variable"] == "state_fips": + if ( + c["operation"] in ("==", "=") + and int(c["value"]) != state + ): mask[:] = False else: try: - values = state_sim.calculate(c['variable'], map_to='household').values - mask &= apply_op(values, c['operation'], c['value']) + values = state_sim.calculate( + c["variable"], map_to="household" + ).values + mask &= apply_op( + values, c["operation"], c["value"] + ) except Exception: pass if not mask.any(): continue - target_values = state_sim.calculate(target['variable'], map_to='household').values + target_values = state_sim.calculate( + target["variable"], map_to="household" + ).values masked_values = (target_values * mask).astype(np.float32) nonzero = np.where(masked_values != 0)[0] if len(nonzero) > 0: - X[row_idx, col_start + nonzero] = masked_values[nonzero] + X[row_idx, col_start + nonzero] = masked_values[ + nonzero + ] household_id_mapping = {} for cd in self.cds_to_calibrate: key = f"cd{cd}" - household_id_mapping[key] = [f"{hh_id}_{key}" for hh_id in household_ids] + household_id_mapping[key] = [ + f"{hh_id}_{key}" for hh_id in household_ids + ] return targets_df, X.tocsr(), household_id_mapping diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py index dd1fbec3..9218c818 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py @@ -61,8 +61,12 @@ from typing import List from policyengine_us import Microsimulation -from policyengine_us_data.datasets.cps.local_area_calibration.sparse_matrix_builder import SparseMatrixBuilder -from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import get_calculated_variables +from policyengine_us_data.datasets.cps.local_area_calibration.sparse_matrix_builder import ( + SparseMatrixBuilder, +) +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_calculated_variables, +) def test_column_indexing(X_sparse, tracer, test_cds) -> bool: @@ -77,8 +81,8 @@ def test_column_indexing(X_sparse, tracer, test_cds) -> bool: errors = [] test_cases = [] - for cd_idx in [0, len(test_cds)//2, len(test_cds)-1]: - for hh_idx in [0, 100, n_hh-1]: + for cd_idx in [0, len(test_cds) // 2, len(test_cds) - 1]: + for hh_idx in [0, 100, n_hh - 1]: test_cases.append((cd_idx, hh_idx)) for cd_idx, hh_idx in test_cases: @@ -89,18 +93,20 @@ def test_column_indexing(X_sparse, tracer, test_cds) -> bool: positions = tracer.get_household_column_positions(hh_id) pos_col = positions[cd] - if col_info['cd_geoid'] != cd: + if col_info["cd_geoid"] != cd: errors.append(f"CD mismatch at col {expected_col}") - if col_info['household_index'] != hh_idx: + if col_info["household_index"] != hh_idx: errors.append(f"HH index mismatch at col {expected_col}") - if col_info['household_id'] != hh_id: + if col_info["household_id"] != hh_id: errors.append(f"HH ID mismatch at col {expected_col}") if pos_col != expected_col: errors.append(f"Position mismatch for hh {hh_id}, cd {cd}") expected_cols = len(test_cds) * n_hh if X_sparse.shape[1] != expected_cols: - errors.append(f"Matrix width mismatch: expected {expected_cols}, got {X_sparse.shape[1]}") + errors.append( + f"Matrix width mismatch: expected {expected_cols}, got {X_sparse.shape[1]}" + ) if errors: print("X Column indexing FAILED:") @@ -108,12 +114,22 @@ def test_column_indexing(X_sparse, tracer, test_cds) -> bool: print(f" {e}") return False - print(f"[PASS] Column indexing: {len(test_cases)} cases, {len(test_cds)} CDs x {n_hh} households") + print( + f"[PASS] Column indexing: {len(test_cases)} cases, {len(test_cds)} CDs x {n_hh} households" + ) return True -def test_same_state_matches_original(X_sparse, targets_df, tracer, sim, test_cds, - dataset_path, n_samples=200, seed=42) -> bool: +def test_same_state_matches_original( + X_sparse, + targets_df, + tracer, + sim, + test_cds, + dataset_path, + n_samples=200, + seed=42, +) -> bool: """ Test 2: Same-state non-zero cells must match fresh same-state simulation. @@ -127,10 +143,13 @@ def test_same_state_matches_original(X_sparse, targets_df, tracer, sim, test_cds hh_states = sim.calculate("state_fips", map_to="household").values state_sims = {} + def get_state_sim(state): if state not in state_sims: s = Microsimulation(dataset=dataset_path) - s.set_input("state_fips", 2023, np.full(n_hh, state, dtype=np.int32)) + s.set_input( + "state_fips", 2023, np.full(n_hh, state, dtype=np.int32) + ) for var in get_calculated_variables(s): s.delete_arrays(var) state_sims[state] = s @@ -153,7 +172,11 @@ def get_state_sim(state): print("[WARN] No same-state non-zero cells found") return True - sample_idx = rng.choice(same_state_indices, min(n_samples, len(same_state_indices)), replace=False) + sample_idx = rng.choice( + same_state_indices, + min(n_samples, len(same_state_indices)), + replace=False, + ) errors = [] for idx in sample_idx: @@ -163,31 +186,48 @@ def get_state_sim(state): hh_idx = col_idx % n_hh cd = test_cds[cd_idx] dest_state = int(cd) // 100 - variable = targets_df.iloc[row_idx]['variable'] + variable = targets_df.iloc[row_idx]["variable"] actual = float(X_sparse[row_idx, col_idx]) state_sim = get_state_sim(dest_state) - expected = float(state_sim.calculate(variable, map_to='household').values[hh_idx]) + expected = float( + state_sim.calculate(variable, map_to="household").values[hh_idx] + ) if not np.isclose(actual, expected, atol=0.5): - errors.append({ - 'hh_id': hh_ids[hh_idx], - 'variable': variable, - 'actual': actual, - 'expected': expected - }) + errors.append( + { + "hh_id": hh_ids[hh_idx], + "variable": variable, + "actual": actual, + "expected": expected, + } + ) if errors: - print(f"X Same-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches") + print( + f"X Same-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches" + ) for e in errors[:5]: - print(f" hh={e['hh_id']}, var={e['variable']}: {e['actual']:.2f} vs {e['expected']:.2f}") + print( + f" hh={e['hh_id']}, var={e['variable']}: {e['actual']:.2f} vs {e['expected']:.2f}" + ) return False - print(f"[PASS] Same-state: {len(sample_idx)}/{len(sample_idx)} match fresh same-state simulation") + print( + f"[PASS] Same-state: {len(sample_idx)}/{len(sample_idx)} match fresh same-state simulation" + ) return True -def test_cross_state_matches_swapped_sim(X_sparse, targets_df, tracer, test_cds, - dataset_path, n_samples=200, seed=42) -> bool: +def test_cross_state_matches_swapped_sim( + X_sparse, + targets_df, + tracer, + test_cds, + dataset_path, + n_samples=200, + seed=42, +) -> bool: """ Test 3: Cross-state non-zero cells must match state-swapped simulation. @@ -201,10 +241,13 @@ def test_cross_state_matches_swapped_sim(X_sparse, targets_df, tracer, test_cds, hh_states = sim_orig.calculate("state_fips", map_to="household").values state_sims = {} + def get_state_sim(state): if state not in state_sims: s = Microsimulation(dataset=dataset_path) - s.set_input("state_fips", 2023, np.full(n_hh, state, dtype=np.int32)) + s.set_input( + "state_fips", 2023, np.full(n_hh, state, dtype=np.int32) + ) for var in get_calculated_variables(s): s.delete_arrays(var) state_sims[state] = s @@ -227,7 +270,11 @@ def get_state_sim(state): print("[WARN] No cross-state non-zero cells found") return True - sample_idx = rng.choice(cross_state_indices, min(n_samples, len(cross_state_indices)), replace=False) + sample_idx = rng.choice( + cross_state_indices, + min(n_samples, len(cross_state_indices)), + replace=False, + ) errors = [] for idx in sample_idx: @@ -237,33 +284,44 @@ def get_state_sim(state): hh_idx = col_idx % n_hh cd = test_cds[cd_idx] dest_state = int(cd) // 100 - variable = targets_df.iloc[row_idx]['variable'] + variable = targets_df.iloc[row_idx]["variable"] actual = float(X_sparse[row_idx, col_idx]) state_sim = get_state_sim(dest_state) - expected = float(state_sim.calculate(variable, map_to='household').values[hh_idx]) + expected = float( + state_sim.calculate(variable, map_to="household").values[hh_idx] + ) if not np.isclose(actual, expected, atol=0.5): - errors.append({ - 'hh_id': hh_ids[hh_idx], - 'orig_state': int(hh_states[hh_idx]), - 'dest_state': dest_state, - 'variable': variable, - 'actual': actual, - 'expected': expected - }) + errors.append( + { + "hh_id": hh_ids[hh_idx], + "orig_state": int(hh_states[hh_idx]), + "dest_state": dest_state, + "variable": variable, + "actual": actual, + "expected": expected, + } + ) if errors: - print(f"X Cross-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches") + print( + f"X Cross-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches" + ) for e in errors[:5]: - print(f" hh={e['hh_id']}, {e['orig_state']}->{e['dest_state']}: {e['actual']:.2f} vs {e['expected']:.2f}") + print( + f" hh={e['hh_id']}, {e['orig_state']}->{e['dest_state']}: {e['actual']:.2f} vs {e['expected']:.2f}" + ) return False - print(f"[PASS] Cross-state: {len(sample_idx)}/{len(sample_idx)} match state-swapped simulation") + print( + f"[PASS] Cross-state: {len(sample_idx)}/{len(sample_idx)} match state-swapped simulation" + ) return True -def test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds, - n_samples=100, seed=42) -> bool: +def test_state_level_zero_masking( + X_sparse, targets_df, tracer, test_cds, n_samples=100, seed=42 +) -> bool: """ Test 4: State-level targets have zeros for wrong-state CD columns. @@ -275,8 +333,8 @@ def test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds, state_targets = [] for row_idx in range(len(targets_df)): - geo_id = targets_df.iloc[row_idx].get('geographic_id', 'US') - if geo_id != 'US': + geo_id = targets_df.iloc[row_idx].get("geographic_id", "US") + if geo_id != "US": try: val = int(geo_id) if val < 100: @@ -290,16 +348,23 @@ def test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds, errors = [] checked = 0 - sample_targets = rng.choice(len(state_targets), min(20, len(state_targets)), replace=False) + sample_targets = rng.choice( + len(state_targets), min(20, len(state_targets)), replace=False + ) for idx in sample_targets: row_idx, target_state = state_targets[idx] - other_state_cds = [(i, cd) for i, cd in enumerate(test_cds) - if int(cd) // 100 != target_state] + other_state_cds = [ + (i, cd) + for i, cd in enumerate(test_cds) + if int(cd) // 100 != target_state + ] if not other_state_cds: continue - sample_cds = rng.choice(len(other_state_cds), min(5, len(other_state_cds)), replace=False) + sample_cds = rng.choice( + len(other_state_cds), min(5, len(other_state_cds)), replace=False + ) for cd_sample_idx in sample_cds: cd_idx, cd = other_state_cds[cd_sample_idx] sample_hh = rng.choice(n_hh, min(5, n_hh), replace=False) @@ -308,17 +373,25 @@ def test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds, actual = X_sparse[row_idx, col_idx] checked += 1 if actual != 0: - errors.append({'row': row_idx, 'cd': cd, 'value': float(actual)}) + errors.append( + {"row": row_idx, "cd": cd, "value": float(actual)} + ) if errors: - print(f"X State-level masking FAILED: {len(errors)}/{checked} should be zero") + print( + f"X State-level masking FAILED: {len(errors)}/{checked} should be zero" + ) return False - print(f"[PASS] State-level masking: {checked}/{checked} wrong-state cells are zero") + print( + f"[PASS] State-level masking: {checked}/{checked} wrong-state cells are zero" + ) return True -def test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds, seed=42) -> bool: +def test_cd_level_zero_masking( + X_sparse, targets_df, tracer, test_cds, seed=42 +) -> bool: """ Test 5: CD-level targets have zeros for other CDs, even same-state. @@ -333,21 +406,28 @@ def test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds, seed=42) cd_targets_with_same_state = [] for row_idx in range(len(targets_df)): - geo_id = targets_df.iloc[row_idx].get('geographic_id', 'US') - if geo_id != 'US': + geo_id = targets_df.iloc[row_idx].get("geographic_id", "US") + if geo_id != "US": try: val = int(geo_id) if val >= 100: target_state = val // 100 - same_state_other_cds = [cd for cd in test_cds - if int(cd) // 100 == target_state and cd != geo_id] + same_state_other_cds = [ + cd + for cd in test_cds + if int(cd) // 100 == target_state and cd != geo_id + ] if same_state_other_cds: - cd_targets_with_same_state.append((row_idx, geo_id, same_state_other_cds)) + cd_targets_with_same_state.append( + (row_idx, geo_id, same_state_other_cds) + ) except (ValueError, TypeError): pass if not cd_targets_with_same_state: - print("[WARN] No CD-level targets with same-state other CDs in test_cds") + print( + "[WARN] No CD-level targets with same-state other CDs in test_cds" + ) return True errors = [] @@ -361,20 +441,33 @@ def test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds, seed=42) actual = X_sparse[row_idx, col_idx] same_state_checks += 1 if actual != 0: - errors.append({'target_cd': target_cd, 'other_cd': cd, 'value': float(actual)}) + errors.append( + { + "target_cd": target_cd, + "other_cd": cd, + "value": float(actual), + } + ) if errors: - print(f"X CD-level masking FAILED: {len(errors)} same-state-different-CD non-zero values") + print( + f"X CD-level masking FAILED: {len(errors)} same-state-different-CD non-zero values" + ) for e in errors[:5]: - print(f" target={e['target_cd']}, other={e['other_cd']}, value={e['value']}") + print( + f" target={e['target_cd']}, other={e['other_cd']}, value={e['value']}" + ) return False - print(f"[PASS] CD-level masking: {same_state_checks} same-state-different-CD checks, all zero") + print( + f"[PASS] CD-level masking: {same_state_checks} same-state-different-CD checks, all zero" + ) return True -def test_national_no_geo_masking(X_sparse, targets_df, tracer, sim, test_cds, - dataset_path, seed=42) -> bool: +def test_national_no_geo_masking( + X_sparse, targets_df, tracer, sim, test_cds, dataset_path, seed=42 +) -> bool: """ Test 6: National targets have no geographic masking. @@ -395,32 +488,41 @@ def test_national_no_geo_masking(X_sparse, targets_df, tracer, sim, test_cds, n_hh = tracer.n_households hh_ids = tracer.original_household_ids - national_rows = [i for i in range(len(targets_df)) - if targets_df.iloc[i].get('geographic_id', 'US') == 'US'] + national_rows = [ + i + for i in range(len(targets_df)) + if targets_df.iloc[i].get("geographic_id", "US") == "US" + ] if not national_rows: print("[WARN] No national targets found") return True states_in_test = sorted(set(int(cd) // 100 for cd in test_cds)) - cds_by_state = {state: [cd for cd in test_cds if int(cd) // 100 == state] - for state in states_in_test} + cds_by_state = { + state: [cd for cd in test_cds if int(cd) // 100 == state] + for state in states_in_test + } print(f" States in test: {states_in_test}") for row_idx in national_rows: - variable = targets_df.iloc[row_idx]['variable'] + variable = targets_df.iloc[row_idx]["variable"] # Find households with non-zero values in this national target row_data = X_sparse.getrow(row_idx) nonzero_cols = row_data.nonzero()[1] if len(nonzero_cols) == 0: - print(f"X National target row {row_idx} ({variable}) has no non-zero values!") + print( + f"X National target row {row_idx} ({variable}) has no non-zero values!" + ) return False # Pick a few households that have non-zero values - sample_cols = rng.choice(nonzero_cols, min(5, len(nonzero_cols)), replace=False) + sample_cols = rng.choice( + nonzero_cols, min(5, len(nonzero_cols)), replace=False + ) households_checked = 0 households_with_multi_state_values = 0 @@ -443,14 +545,20 @@ def test_national_no_geo_masking(X_sparse, targets_df, tracer, sim, test_cds, if len(values_by_state) > 1: households_with_multi_state_values += 1 - print(f" Row {row_idx} ({variable}): {households_with_multi_state_values}/{households_checked} " - f"households have values in multiple states") + print( + f" Row {row_idx} ({variable}): {households_with_multi_state_values}/{households_checked} " + f"households have values in multiple states" + ) - print(f"[PASS] National targets: no geographic masking, values vary by destination state") + print( + f"[PASS] National targets: no geographic masking, values vary by destination state" + ) return True -def run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) -> bool: +def run_all_tests( + X_sparse, targets_df, tracer, sim, test_cds, dataset_path +) -> bool: """Run all verification tests and return overall pass/fail.""" print("=" * 70) print("SPARSE MATRIX VERIFICATION TESTS") @@ -462,19 +570,35 @@ def run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) -> results.append(test_column_indexing(X_sparse, tracer, test_cds)) print("\n[Test 2] Same-State Values Match Fresh Sim") - results.append(test_same_state_matches_original(X_sparse, targets_df, tracer, sim, test_cds, dataset_path)) + results.append( + test_same_state_matches_original( + X_sparse, targets_df, tracer, sim, test_cds, dataset_path + ) + ) print("\n[Test 3] Cross-State Values Match State-Swapped Sim") - results.append(test_cross_state_matches_swapped_sim(X_sparse, targets_df, tracer, test_cds, dataset_path)) + results.append( + test_cross_state_matches_swapped_sim( + X_sparse, targets_df, tracer, test_cds, dataset_path + ) + ) print("\n[Test 4] State-Level Zero Masking") - results.append(test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds)) + results.append( + test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds) + ) print("\n[Test 5] CD-Level Zero Masking (Same-State-Different-CD)") - results.append(test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds)) + results.append( + test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds) + ) print("\n[Test 6] National Targets No Geo Masking") - results.append(test_national_no_geo_masking(X_sparse, targets_df, tracer, sim, test_cds, dataset_path)) + results.append( + test_national_no_geo_masking( + X_sparse, targets_df, tracer, sim, test_cds, dataset_path + ) + ) print("\n" + "=" * 70) passed = sum(results) @@ -491,7 +615,9 @@ def run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) -> if __name__ == "__main__": from sqlalchemy import create_engine, text from policyengine_us_data.storage import STORAGE_FOLDER - from policyengine_us_data.datasets.cps.local_area_calibration.matrix_tracer import MatrixTracer + from policyengine_us_data.datasets.cps.local_area_calibration.matrix_tracer import ( + MatrixTracer, + ) print("Setting up verification tests...") @@ -523,20 +649,24 @@ def run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) -> sim = Microsimulation(dataset=dataset_path) builder = SparseMatrixBuilder( - db_uri, time_period=2023, + db_uri, + time_period=2023, cds_to_calibrate=test_cds, - dataset_path=dataset_path + dataset_path=dataset_path, ) print("Building sparse matrix...") targets_df, X_sparse, household_id_mapping = builder.build_matrix( - sim, - target_filter={"stratum_group_ids": [4], "variables": ["snap"]} + sim, target_filter={"stratum_group_ids": [4], "variables": ["snap"]} ) - tracer = MatrixTracer(targets_df, X_sparse, household_id_mapping, test_cds, sim) + tracer = MatrixTracer( + targets_df, X_sparse, household_id_mapping, test_cds, sim + ) print(f"Matrix shape: {X_sparse.shape}, non-zero: {X_sparse.nnz}\n") - success = run_all_tests(X_sparse, targets_df, tracer, sim, test_cds, dataset_path) + success = run_all_tests( + X_sparse, targets_df, tracer, sim, test_cds, dataset_path + ) exit(0 if success else 1) diff --git a/policyengine_us_data/datasets/puf/puf.py b/policyengine_us_data/datasets/puf/puf.py index 99d93e4a..3cb72efa 100644 --- a/policyengine_us_data/datasets/puf/puf.py +++ b/policyengine_us_data/datasets/puf/puf.py @@ -756,6 +756,7 @@ class PUF_2024(PUF): if __name__ == "__main__": import os + geo_stacking = os.environ.get("GEO_STACKING") == "true" if geo_stacking: From 06098a507b14fed14df200114e2ba80d04a0a1e2 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 5 Dec 2025 13:45:36 -0500 Subject: [PATCH 03/24] Refactor tests and fix enum encoding, minimize PR scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move sparse matrix tests to tests/test_local_area_calibration/ - Split large test file into focused modules (column indexing, same-state, cross-state, geo masking) - Fix small_enhanced_cps.py enum encoding (decode_to_str before astype) - Fix create_stratified_cps.py to use local storage instead of HuggingFace - Remove CPS_2024_Full to keep PR minimal - Revert ExtendedCPS_2024 to use CPS_2024 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 2 +- policyengine_us_data/datasets/cps/cps.py | 10 - .../datasets/cps/extended_cps.py | 2 +- .../create_stratified_cps.py | 8 +- .../test_sparse_matrix_builder.py | 672 ------------------ .../datasets/cps/small_enhanced_cps.py | 8 +- .../test_local_area_calibration/__init__.py | 0 .../test_local_area_calibration/conftest.py | 119 ++++ .../test_column_indexing.py | 47 ++ .../test_cross_state.py | 101 +++ .../test_geo_masking.py | 199 ++++++ .../test_same_state.py | 99 +++ 12 files changed, 580 insertions(+), 687 deletions(-) delete mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/__init__.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/conftest.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_column_indexing.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_cross_state.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_geo_masking.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_same_state.py diff --git a/changelog_entry.yaml b/changelog_entry.yaml index b6da5347..84dc236e 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -3,6 +3,6 @@ added: - Sparse matrix builder for local area calibration with database-driven constraints - Geo-stacking data pipeline (make data-geo) for congressional district calibration - - ExtendedCPS_2023, PUF_2023, CPS_2024_Full dataset classes + - ExtendedCPS_2023 and PUF_2023 dataset classes - Stratified CPS sampling to preserve high-income households - Matrix verification tests for geo-stacking calibration diff --git a/policyengine_us_data/datasets/cps/cps.py b/policyengine_us_data/datasets/cps/cps.py index aa456f23..ac464632 100644 --- a/policyengine_us_data/datasets/cps/cps.py +++ b/policyengine_us_data/datasets/cps/cps.py @@ -2058,15 +2058,6 @@ class CPS_2023_Full(CPS): time_period = 2023 -class CPS_2024_Full(CPS): - name = "cps_2024_full" - label = "CPS 2024 (full)" - raw_cps = CensusCPS_2024 - previous_year_raw_cps = CensusCPS_2023 - file_path = STORAGE_FOLDER / "cps_2024_full.h5" - time_period = 2024 - - class PooledCPS(Dataset): data_format = Dataset.ARRAYS input_datasets: list @@ -2144,5 +2135,4 @@ class Pooled_3_Year_CPS_2023(PooledCPS): CPS_2021_Full().generate() CPS_2022_Full().generate() CPS_2023_Full().generate() - CPS_2024_Full().generate() Pooled_3_Year_CPS_2023().generate() diff --git a/policyengine_us_data/datasets/cps/extended_cps.py b/policyengine_us_data/datasets/cps/extended_cps.py index e83b7015..dace9d5f 100644 --- a/policyengine_us_data/datasets/cps/extended_cps.py +++ b/policyengine_us_data/datasets/cps/extended_cps.py @@ -330,7 +330,7 @@ class ExtendedCPS_2023(ExtendedCPS): class ExtendedCPS_2024(ExtendedCPS): - cps = CPS_2024_Full + cps = CPS_2024 puf = PUF_2024 name = "extended_cps_2024" label = "Extended CPS (2024)" diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py index cb85d082..8dccf34a 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py @@ -19,7 +19,7 @@ def create_stratified_cps_dataset( target_households=30_000, high_income_percentile=99, # Keep ALL households above this percentile - base_dataset="hf://policyengine/test/extended_cps_2023.h5", + base_dataset=None, output_path=None, ): """ @@ -34,6 +34,12 @@ def create_stratified_cps_dataset( print("CREATING STRATIFIED CPS DATASET") print("=" * 70) + # Default to local storage if no base_dataset specified + if base_dataset is None: + from policyengine_us_data.storage import STORAGE_FOLDER + + base_dataset = str(STORAGE_FOLDER / "extended_cps_2023.h5") + # Load the original simulation print("Loading original dataset...") sim = Microsimulation(dataset=base_dataset) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py deleted file mode 100644 index 9218c818..00000000 --- a/policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py +++ /dev/null @@ -1,672 +0,0 @@ -""" -Verification tests for the sparse matrix builder. - -RATIONALE -========= -The sparse matrix X_sparse contains pre-calculated values for households -"transplanted" to different congressional districts. When a household moves -to a CD in a different state, state-dependent benefits like SNAP are -recalculated under the destination state's rules. - -This creates a verification challenge: we can't easily verify that SNAP -*should* be $11,560 in NC vs $14,292 in AK without reimplementing the -entire SNAP formula. However, we CAN verify: - -1. CONSISTENCY: X_sparse values match an independently-created simulation - with state_fips set to the destination state. This confirms the sparse - matrix builder correctly uses PolicyEngine's calculation engine. - -2. SAME-STATE INVARIANCE: When a household's original state equals the - destination CD's state, the value should exactly match the original - simulation. Any mismatch here is definitively a bug (not a policy difference). - -3. GEOGRAPHIC MASKING: Zero cells should be zero because of geographic - constraint mismatches: - - State-level targets: only CDs in that state have non-zero values - - CD-level targets: only that specific CD has non-zero values (even - same-state different-CD columns should be zero) - - National targets: NO geographic masking - all CD columns can have - non-zero values, but values DIFFER by destination state because - benefits are recalculated under each state's rules - -By verifying these properties, we confirm the sparse matrix builder is -working correctly without needing to understand every state-specific -policy formula. - -CACHE CLEARING LESSON -===================== -When setting state_fips via set_input(), you MUST clear cached calculated -variables to force recalculation. Use get_calculated_variables() which -returns variables with formulas - these are the ones that need recalculation. - -DO NOT use `var not in sim.input_variables` - this misses variables that -are BOTH inputs AND have formulas (12 such variables exist). If any of -these are in the dependency chain, the recalculation will use stale values. - -Correct pattern: - sim.set_input("state_fips", period, new_values) - for var in get_calculated_variables(sim): - sim.delete_arrays(var) - -USAGE -===== -Run interactively or with pytest: - - python test_sparse_matrix_builder.py - pytest test_sparse_matrix_builder.py -v -""" - -import numpy as np -import pandas as pd -from typing import List - -from policyengine_us import Microsimulation -from policyengine_us_data.datasets.cps.local_area_calibration.sparse_matrix_builder import ( - SparseMatrixBuilder, -) -from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( - get_calculated_variables, -) - - -def test_column_indexing(X_sparse, tracer, test_cds) -> bool: - """ - Test 1: Verify column indexing roundtrip. - - Column index = cd_idx * n_households + household_index - This is pure math - if this fails, everything else is unreliable. - """ - n_hh = tracer.n_households - hh_ids = tracer.original_household_ids - errors = [] - - test_cases = [] - for cd_idx in [0, len(test_cds) // 2, len(test_cds) - 1]: - for hh_idx in [0, 100, n_hh - 1]: - test_cases.append((cd_idx, hh_idx)) - - for cd_idx, hh_idx in test_cases: - cd = test_cds[cd_idx] - hh_id = hh_ids[hh_idx] - expected_col = cd_idx * n_hh + hh_idx - col_info = tracer.get_column_info(expected_col) - positions = tracer.get_household_column_positions(hh_id) - pos_col = positions[cd] - - if col_info["cd_geoid"] != cd: - errors.append(f"CD mismatch at col {expected_col}") - if col_info["household_index"] != hh_idx: - errors.append(f"HH index mismatch at col {expected_col}") - if col_info["household_id"] != hh_id: - errors.append(f"HH ID mismatch at col {expected_col}") - if pos_col != expected_col: - errors.append(f"Position mismatch for hh {hh_id}, cd {cd}") - - expected_cols = len(test_cds) * n_hh - if X_sparse.shape[1] != expected_cols: - errors.append( - f"Matrix width mismatch: expected {expected_cols}, got {X_sparse.shape[1]}" - ) - - if errors: - print("X Column indexing FAILED:") - for e in errors: - print(f" {e}") - return False - - print( - f"[PASS] Column indexing: {len(test_cases)} cases, {len(test_cds)} CDs x {n_hh} households" - ) - return True - - -def test_same_state_matches_original( - X_sparse, - targets_df, - tracer, - sim, - test_cds, - dataset_path, - n_samples=200, - seed=42, -) -> bool: - """ - Test 2: Same-state non-zero cells must match fresh same-state simulation. - - When household stays in same state, X_sparse should contain the value - calculated from a fresh simulation with state_fips set to that state - (same as the matrix builder does). - """ - rng = np.random.default_rng(seed) - n_hh = tracer.n_households - hh_ids = tracer.original_household_ids - hh_states = sim.calculate("state_fips", map_to="household").values - - state_sims = {} - - def get_state_sim(state): - if state not in state_sims: - s = Microsimulation(dataset=dataset_path) - s.set_input( - "state_fips", 2023, np.full(n_hh, state, dtype=np.int32) - ) - for var in get_calculated_variables(s): - s.delete_arrays(var) - state_sims[state] = s - return state_sims[state] - - nonzero_rows, nonzero_cols = X_sparse.nonzero() - - same_state_indices = [] - for i in range(len(nonzero_rows)): - col_idx = nonzero_cols[i] - cd_idx = col_idx // n_hh - hh_idx = col_idx % n_hh - cd = test_cds[cd_idx] - dest_state = int(cd) // 100 - orig_state = int(hh_states[hh_idx]) - if dest_state == orig_state: - same_state_indices.append(i) - - if not same_state_indices: - print("[WARN] No same-state non-zero cells found") - return True - - sample_idx = rng.choice( - same_state_indices, - min(n_samples, len(same_state_indices)), - replace=False, - ) - errors = [] - - for idx in sample_idx: - row_idx = nonzero_rows[idx] - col_idx = nonzero_cols[idx] - cd_idx = col_idx // n_hh - hh_idx = col_idx % n_hh - cd = test_cds[cd_idx] - dest_state = int(cd) // 100 - variable = targets_df.iloc[row_idx]["variable"] - actual = float(X_sparse[row_idx, col_idx]) - state_sim = get_state_sim(dest_state) - expected = float( - state_sim.calculate(variable, map_to="household").values[hh_idx] - ) - - if not np.isclose(actual, expected, atol=0.5): - errors.append( - { - "hh_id": hh_ids[hh_idx], - "variable": variable, - "actual": actual, - "expected": expected, - } - ) - - if errors: - print( - f"X Same-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches" - ) - for e in errors[:5]: - print( - f" hh={e['hh_id']}, var={e['variable']}: {e['actual']:.2f} vs {e['expected']:.2f}" - ) - return False - - print( - f"[PASS] Same-state: {len(sample_idx)}/{len(sample_idx)} match fresh same-state simulation" - ) - return True - - -def test_cross_state_matches_swapped_sim( - X_sparse, - targets_df, - tracer, - test_cds, - dataset_path, - n_samples=200, - seed=42, -) -> bool: - """ - Test 3: Cross-state non-zero cells must match state-swapped simulation. - - When household moves to different state, X_sparse should contain the - value calculated from a fresh simulation with state_fips set to destination state. - """ - rng = np.random.default_rng(seed) - sim_orig = Microsimulation(dataset=dataset_path) - n_hh = tracer.n_households - hh_ids = tracer.original_household_ids - hh_states = sim_orig.calculate("state_fips", map_to="household").values - - state_sims = {} - - def get_state_sim(state): - if state not in state_sims: - s = Microsimulation(dataset=dataset_path) - s.set_input( - "state_fips", 2023, np.full(n_hh, state, dtype=np.int32) - ) - for var in get_calculated_variables(s): - s.delete_arrays(var) - state_sims[state] = s - return state_sims[state] - - nonzero_rows, nonzero_cols = X_sparse.nonzero() - - cross_state_indices = [] - for i in range(len(nonzero_rows)): - col_idx = nonzero_cols[i] - cd_idx = col_idx // n_hh - hh_idx = col_idx % n_hh - cd = test_cds[cd_idx] - dest_state = int(cd) // 100 - orig_state = int(hh_states[hh_idx]) - if dest_state != orig_state: - cross_state_indices.append(i) - - if not cross_state_indices: - print("[WARN] No cross-state non-zero cells found") - return True - - sample_idx = rng.choice( - cross_state_indices, - min(n_samples, len(cross_state_indices)), - replace=False, - ) - errors = [] - - for idx in sample_idx: - row_idx = nonzero_rows[idx] - col_idx = nonzero_cols[idx] - cd_idx = col_idx // n_hh - hh_idx = col_idx % n_hh - cd = test_cds[cd_idx] - dest_state = int(cd) // 100 - variable = targets_df.iloc[row_idx]["variable"] - actual = float(X_sparse[row_idx, col_idx]) - state_sim = get_state_sim(dest_state) - expected = float( - state_sim.calculate(variable, map_to="household").values[hh_idx] - ) - - if not np.isclose(actual, expected, atol=0.5): - errors.append( - { - "hh_id": hh_ids[hh_idx], - "orig_state": int(hh_states[hh_idx]), - "dest_state": dest_state, - "variable": variable, - "actual": actual, - "expected": expected, - } - ) - - if errors: - print( - f"X Cross-state verification FAILED: {len(errors)}/{len(sample_idx)} mismatches" - ) - for e in errors[:5]: - print( - f" hh={e['hh_id']}, {e['orig_state']}->{e['dest_state']}: {e['actual']:.2f} vs {e['expected']:.2f}" - ) - return False - - print( - f"[PASS] Cross-state: {len(sample_idx)}/{len(sample_idx)} match state-swapped simulation" - ) - return True - - -def test_state_level_zero_masking( - X_sparse, targets_df, tracer, test_cds, n_samples=100, seed=42 -) -> bool: - """ - Test 4: State-level targets have zeros for wrong-state CD columns. - - For a target with geographic_id=37 (NC), columns for CDs in other states - (HI, MT, AK) should all be zero. - """ - rng = np.random.default_rng(seed) - n_hh = tracer.n_households - - state_targets = [] - for row_idx in range(len(targets_df)): - geo_id = targets_df.iloc[row_idx].get("geographic_id", "US") - if geo_id != "US": - try: - val = int(geo_id) - if val < 100: - state_targets.append((row_idx, val)) - except (ValueError, TypeError): - pass - - if not state_targets: - print("[WARN] No state-level targets found") - return True - - errors = [] - checked = 0 - sample_targets = rng.choice( - len(state_targets), min(20, len(state_targets)), replace=False - ) - - for idx in sample_targets: - row_idx, target_state = state_targets[idx] - other_state_cds = [ - (i, cd) - for i, cd in enumerate(test_cds) - if int(cd) // 100 != target_state - ] - if not other_state_cds: - continue - - sample_cds = rng.choice( - len(other_state_cds), min(5, len(other_state_cds)), replace=False - ) - for cd_sample_idx in sample_cds: - cd_idx, cd = other_state_cds[cd_sample_idx] - sample_hh = rng.choice(n_hh, min(5, n_hh), replace=False) - for hh_idx in sample_hh: - col_idx = cd_idx * n_hh + hh_idx - actual = X_sparse[row_idx, col_idx] - checked += 1 - if actual != 0: - errors.append( - {"row": row_idx, "cd": cd, "value": float(actual)} - ) - - if errors: - print( - f"X State-level masking FAILED: {len(errors)}/{checked} should be zero" - ) - return False - - print( - f"[PASS] State-level masking: {checked}/{checked} wrong-state cells are zero" - ) - return True - - -def test_cd_level_zero_masking( - X_sparse, targets_df, tracer, test_cds, seed=42 -) -> bool: - """ - Test 5: CD-level targets have zeros for other CDs, even same-state. - - For a target with geographic_id=3707, columns for CDs 3701-3706, 3708-3714 - should all be zero, even though they're all in NC (state 37). - - Note: Requires test_cds to include multiple CDs from the same state as - some CD-level target geographic_ids. - """ - rng = np.random.default_rng(seed) - n_hh = tracer.n_households - - cd_targets_with_same_state = [] - for row_idx in range(len(targets_df)): - geo_id = targets_df.iloc[row_idx].get("geographic_id", "US") - if geo_id != "US": - try: - val = int(geo_id) - if val >= 100: - target_state = val // 100 - same_state_other_cds = [ - cd - for cd in test_cds - if int(cd) // 100 == target_state and cd != geo_id - ] - if same_state_other_cds: - cd_targets_with_same_state.append( - (row_idx, geo_id, same_state_other_cds) - ) - except (ValueError, TypeError): - pass - - if not cd_targets_with_same_state: - print( - "[WARN] No CD-level targets with same-state other CDs in test_cds" - ) - return True - - errors = [] - same_state_checks = 0 - - for row_idx, target_cd, other_cds in cd_targets_with_same_state[:10]: - for cd in other_cds: - cd_idx = test_cds.index(cd) - for hh_idx in rng.choice(n_hh, 3, replace=False): - col_idx = cd_idx * n_hh + hh_idx - actual = X_sparse[row_idx, col_idx] - same_state_checks += 1 - if actual != 0: - errors.append( - { - "target_cd": target_cd, - "other_cd": cd, - "value": float(actual), - } - ) - - if errors: - print( - f"X CD-level masking FAILED: {len(errors)} same-state-different-CD non-zero values" - ) - for e in errors[:5]: - print( - f" target={e['target_cd']}, other={e['other_cd']}, value={e['value']}" - ) - return False - - print( - f"[PASS] CD-level masking: {same_state_checks} same-state-different-CD checks, all zero" - ) - return True - - -def test_national_no_geo_masking( - X_sparse, targets_df, tracer, sim, test_cds, dataset_path, seed=42 -) -> bool: - """ - Test 6: National targets have no geographic masking. - - National targets (geographic_id='US') can have non-zero values for ANY CD. - Moreover, values DIFFER by destination state because benefits are - recalculated under each state's rules. - - Example: Household 177332 (originally AK with SNAP=$14,292) - - X_sparse[national_row, AK_CD_col] = $14,292 (staying in AK) - - X_sparse[national_row, NC_CD_col] = $11,560 (recalculated for NC) - - We verify by: - 1. Finding households with non-zero values in the national target - 2. Checking they have values in multiple states' CD columns - 3. Confirming values differ between states (due to recalculation) - """ - rng = np.random.default_rng(seed) - n_hh = tracer.n_households - hh_ids = tracer.original_household_ids - - national_rows = [ - i - for i in range(len(targets_df)) - if targets_df.iloc[i].get("geographic_id", "US") == "US" - ] - - if not national_rows: - print("[WARN] No national targets found") - return True - - states_in_test = sorted(set(int(cd) // 100 for cd in test_cds)) - cds_by_state = { - state: [cd for cd in test_cds if int(cd) // 100 == state] - for state in states_in_test - } - - print(f" States in test: {states_in_test}") - - for row_idx in national_rows: - variable = targets_df.iloc[row_idx]["variable"] - - # Find households with non-zero values in this national target - row_data = X_sparse.getrow(row_idx) - nonzero_cols = row_data.nonzero()[1] - - if len(nonzero_cols) == 0: - print( - f"X National target row {row_idx} ({variable}) has no non-zero values!" - ) - return False - - # Pick a few households that have non-zero values - sample_cols = rng.choice( - nonzero_cols, min(5, len(nonzero_cols)), replace=False - ) - - households_checked = 0 - households_with_multi_state_values = 0 - - for col_idx in sample_cols: - hh_idx = col_idx % n_hh - hh_id = hh_ids[hh_idx] - - # Get this household's values across different states - values_by_state = {} - for state, cds in cds_by_state.items(): - cd = cds[0] # Just check first CD in each state - cd_idx = test_cds.index(cd) - state_col = cd_idx * n_hh + hh_idx - val = float(X_sparse[row_idx, state_col]) - if val != 0: - values_by_state[state] = val - - households_checked += 1 - if len(values_by_state) > 1: - households_with_multi_state_values += 1 - - print( - f" Row {row_idx} ({variable}): {households_with_multi_state_values}/{households_checked} " - f"households have values in multiple states" - ) - - print( - f"[PASS] National targets: no geographic masking, values vary by destination state" - ) - return True - - -def run_all_tests( - X_sparse, targets_df, tracer, sim, test_cds, dataset_path -) -> bool: - """Run all verification tests and return overall pass/fail.""" - print("=" * 70) - print("SPARSE MATRIX VERIFICATION TESTS") - print("=" * 70) - - results = [] - - print("\n[Test 1] Column Indexing") - results.append(test_column_indexing(X_sparse, tracer, test_cds)) - - print("\n[Test 2] Same-State Values Match Fresh Sim") - results.append( - test_same_state_matches_original( - X_sparse, targets_df, tracer, sim, test_cds, dataset_path - ) - ) - - print("\n[Test 3] Cross-State Values Match State-Swapped Sim") - results.append( - test_cross_state_matches_swapped_sim( - X_sparse, targets_df, tracer, test_cds, dataset_path - ) - ) - - print("\n[Test 4] State-Level Zero Masking") - results.append( - test_state_level_zero_masking(X_sparse, targets_df, tracer, test_cds) - ) - - print("\n[Test 5] CD-Level Zero Masking (Same-State-Different-CD)") - results.append( - test_cd_level_zero_masking(X_sparse, targets_df, tracer, test_cds) - ) - - print("\n[Test 6] National Targets No Geo Masking") - results.append( - test_national_no_geo_masking( - X_sparse, targets_df, tracer, sim, test_cds, dataset_path - ) - ) - - print("\n" + "=" * 70) - passed = sum(results) - total = len(results) - if passed == total: - print(f"ALL TESTS PASSED ({passed}/{total})") - else: - print(f"SOME TESTS FAILED ({passed}/{total} passed)") - print("=" * 70) - - return all(results) - - -if __name__ == "__main__": - from sqlalchemy import create_engine, text - from policyengine_us_data.storage import STORAGE_FOLDER - from policyengine_us_data.datasets.cps.local_area_calibration.matrix_tracer import ( - MatrixTracer, - ) - - print("Setting up verification tests...") - - db_path = STORAGE_FOLDER / "policy_data.db" - db_uri = f"sqlite:///{db_path}" - dataset_path = str(STORAGE_FOLDER / "stratified_extended_cps_2023.h5") - - # Test with NC, HI, MT, AK CDs (manageable size, includes same-state CDs for Test 5) - engine = create_engine(db_uri) - query = """ - SELECT DISTINCT sc.value as cd_geoid - FROM strata s - JOIN stratum_constraints sc ON s.stratum_id = sc.stratum_id - WHERE s.stratum_group_id = 1 - AND sc.constraint_variable = 'congressional_district_geoid' - AND ( - sc.value LIKE '37__' - OR sc.value LIKE '150_' - OR sc.value LIKE '300_' - OR sc.value = '200' OR sc.value = '201' - ) - ORDER BY sc.value - """ - with engine.connect() as conn: - result = conn.execute(text(query)).fetchall() - test_cds = [row[0] for row in result] - - print(f"Testing with {len(test_cds)} CDs from 4 states") - - sim = Microsimulation(dataset=dataset_path) - builder = SparseMatrixBuilder( - db_uri, - time_period=2023, - cds_to_calibrate=test_cds, - dataset_path=dataset_path, - ) - - print("Building sparse matrix...") - targets_df, X_sparse, household_id_mapping = builder.build_matrix( - sim, target_filter={"stratum_group_ids": [4], "variables": ["snap"]} - ) - - tracer = MatrixTracer( - targets_df, X_sparse, household_id_mapping, test_cds, sim - ) - - print(f"Matrix shape: {X_sparse.shape}, non-zero: {X_sparse.nnz}\n") - - success = run_all_tests( - X_sparse, targets_df, tracer, sim, test_cds, dataset_path - ) - exit(0 if success else 1) diff --git a/policyengine_us_data/datasets/cps/small_enhanced_cps.py b/policyengine_us_data/datasets/cps/small_enhanced_cps.py index 83a5eba0..a9679a2a 100644 --- a/policyengine_us_data/datasets/cps/small_enhanced_cps.py +++ b/policyengine_us_data/datasets/cps/small_enhanced_cps.py @@ -22,11 +22,15 @@ def create_small_ecps(): data[variable] = {} for time_period in simulation.get_holder(variable).get_known_periods(): values = simulation.get_holder(variable).get_array(time_period) - values = np.array(values) if simulation.tax_benefit_system.variables.get( variable ).value_type in (Enum, str): - values = values.astype("S") + if hasattr(values, "decode_to_str"): + values = values.decode_to_str().astype("S") + else: + values = values.astype("S") + else: + values = np.array(values) if values is not None: data[variable][time_period] = values diff --git a/policyengine_us_data/tests/test_local_area_calibration/__init__.py b/policyengine_us_data/tests/test_local_area_calibration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/policyengine_us_data/tests/test_local_area_calibration/conftest.py b/policyengine_us_data/tests/test_local_area_calibration/conftest.py new file mode 100644 index 00000000..542036b4 --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/conftest.py @@ -0,0 +1,119 @@ +"""Shared fixtures for local area calibration tests.""" + +import pytest +import numpy as np +from sqlalchemy import create_engine, text + +from policyengine_us import Microsimulation +from policyengine_us_data.storage import STORAGE_FOLDER +from policyengine_us_data.datasets.cps.local_area_calibration.sparse_matrix_builder import ( + SparseMatrixBuilder, +) +from policyengine_us_data.datasets.cps.local_area_calibration.matrix_tracer import ( + MatrixTracer, +) +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_calculated_variables, +) + + +@pytest.fixture(scope="module") +def db_uri(): + db_path = STORAGE_FOLDER / "policy_data.db" + return f"sqlite:///{db_path}" + + +@pytest.fixture(scope="module") +def dataset_path(): + return str(STORAGE_FOLDER / "stratified_extended_cps_2023.h5") + + +@pytest.fixture(scope="module") +def test_cds(db_uri): + """CDs from NC, HI, MT, AK (manageable size, multiple same-state CDs).""" + engine = create_engine(db_uri) + query = """ + SELECT DISTINCT sc.value as cd_geoid + FROM strata s + JOIN stratum_constraints sc ON s.stratum_id = sc.stratum_id + WHERE s.stratum_group_id = 1 + AND sc.constraint_variable = 'congressional_district_geoid' + AND ( + sc.value LIKE '37__' + OR sc.value LIKE '150_' + OR sc.value LIKE '300_' + OR sc.value = '200' OR sc.value = '201' + ) + ORDER BY sc.value + """ + with engine.connect() as conn: + result = conn.execute(text(query)).fetchall() + return [row[0] for row in result] + + +@pytest.fixture(scope="module") +def sim(dataset_path): + return Microsimulation(dataset=dataset_path) + + +@pytest.fixture(scope="module") +def matrix_data(db_uri, dataset_path, test_cds, sim): + """Build sparse matrix, return (targets_df, X_sparse, household_id_mapping).""" + builder = SparseMatrixBuilder( + db_uri, + time_period=2023, + cds_to_calibrate=test_cds, + dataset_path=dataset_path, + ) + targets_df, X_sparse, household_id_mapping = builder.build_matrix( + sim, target_filter={"stratum_group_ids": [4], "variables": ["snap"]} + ) + return targets_df, X_sparse, household_id_mapping + + +@pytest.fixture(scope="module") +def targets_df(matrix_data): + return matrix_data[0] + + +@pytest.fixture(scope="module") +def X_sparse(matrix_data): + return matrix_data[1] + + +@pytest.fixture(scope="module") +def household_id_mapping(matrix_data): + return matrix_data[2] + + +@pytest.fixture(scope="module") +def tracer(targets_df, X_sparse, household_id_mapping, test_cds, sim): + return MatrixTracer( + targets_df, X_sparse, household_id_mapping, test_cds, sim + ) + + +@pytest.fixture(scope="module") +def n_households(tracer): + return tracer.n_households + + +@pytest.fixture(scope="module") +def household_ids(tracer): + return tracer.original_household_ids + + +@pytest.fixture(scope="module") +def household_states(sim): + return sim.calculate("state_fips", map_to="household").values + + +def create_state_simulation(dataset_path, n_households, state): + """Create simulation with all households assigned to a specific state.""" + s = Microsimulation(dataset=dataset_path) + s.set_input( + "state_fips", 2023, np.full(n_households, state, dtype=np.int32) + ) + for var in get_calculated_variables(s): + s.delete_arrays(var) + return s diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_column_indexing.py b/policyengine_us_data/tests/test_local_area_calibration/test_column_indexing.py new file mode 100644 index 00000000..2e23763b --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/test_column_indexing.py @@ -0,0 +1,47 @@ +"""Test column indexing in sparse matrix.""" + +import pytest + + +def test_column_indexing_roundtrip(X_sparse, tracer, test_cds): + """ + Verify column index = cd_idx * n_households + household_index. + + This is pure math - if this fails, everything else is unreliable. + """ + n_hh = tracer.n_households + hh_ids = tracer.original_household_ids + errors = [] + + test_cases = [] + for cd_idx in [0, len(test_cds) // 2, len(test_cds) - 1]: + for hh_idx in [0, 100, n_hh - 1]: + test_cases.append((cd_idx, hh_idx)) + + for cd_idx, hh_idx in test_cases: + cd = test_cds[cd_idx] + hh_id = hh_ids[hh_idx] + expected_col = cd_idx * n_hh + hh_idx + col_info = tracer.get_column_info(expected_col) + positions = tracer.get_household_column_positions(hh_id) + pos_col = positions[cd] + + if col_info["cd_geoid"] != cd: + errors.append(f"CD mismatch at col {expected_col}") + if col_info["household_index"] != hh_idx: + errors.append(f"HH index mismatch at col {expected_col}") + if col_info["household_id"] != hh_id: + errors.append(f"HH ID mismatch at col {expected_col}") + if pos_col != expected_col: + errors.append(f"Position mismatch for hh {hh_id}, cd {cd}") + + assert not errors, f"Column indexing errors: {errors}" + + +def test_matrix_dimensions(X_sparse, tracer, test_cds): + """Verify matrix width matches expected CD x household count.""" + n_hh = tracer.n_households + expected_cols = len(test_cds) * n_hh + assert ( + X_sparse.shape[1] == expected_cols + ), f"Matrix width mismatch: expected {expected_cols}, got {X_sparse.shape[1]}" diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_cross_state.py b/policyengine_us_data/tests/test_local_area_calibration/test_cross_state.py new file mode 100644 index 00000000..ea9eca6f --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/test_cross_state.py @@ -0,0 +1,101 @@ +"""Test cross-state values match state-swapped simulations.""" + +import pytest +import numpy as np + +from policyengine_us import Microsimulation +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_calculated_variables, +) + + +def test_cross_state_matches_swapped_sim( + X_sparse, + targets_df, + tracer, + test_cds, + dataset_path, + n_households, + household_ids, + household_states, +): + """ + Cross-state non-zero cells must match state-swapped simulation. + + When household moves to different state, X_sparse should contain the + value calculated from a fresh simulation with state_fips set to + destination state. + """ + n_samples = 200 + seed = 42 + rng = np.random.default_rng(seed) + n_hh = n_households + hh_ids = household_ids + hh_states = household_states + + state_sims = {} + + def get_state_sim(state): + if state not in state_sims: + s = Microsimulation(dataset=dataset_path) + s.set_input( + "state_fips", 2023, np.full(n_hh, state, dtype=np.int32) + ) + for var in get_calculated_variables(s): + s.delete_arrays(var) + state_sims[state] = s + return state_sims[state] + + nonzero_rows, nonzero_cols = X_sparse.nonzero() + + cross_state_indices = [] + for i in range(len(nonzero_rows)): + col_idx = nonzero_cols[i] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + orig_state = int(hh_states[hh_idx]) + if dest_state != orig_state: + cross_state_indices.append(i) + + if not cross_state_indices: + pytest.skip("No cross-state non-zero cells found") + + sample_idx = rng.choice( + cross_state_indices, + min(n_samples, len(cross_state_indices)), + replace=False, + ) + errors = [] + + for idx in sample_idx: + row_idx = nonzero_rows[idx] + col_idx = nonzero_cols[idx] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + variable = targets_df.iloc[row_idx]["variable"] + actual = float(X_sparse[row_idx, col_idx]) + state_sim = get_state_sim(dest_state) + expected = float( + state_sim.calculate(variable, map_to="household").values[hh_idx] + ) + + if not np.isclose(actual, expected, atol=0.5): + errors.append( + { + "hh_id": hh_ids[hh_idx], + "orig_state": int(hh_states[hh_idx]), + "dest_state": dest_state, + "variable": variable, + "actual": actual, + "expected": expected, + } + ) + + assert not errors, ( + f"Cross-state verification failed: {len(errors)}/{len(sample_idx)} " + f"mismatches. First 5: {errors[:5]}" + ) diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_geo_masking.py b/policyengine_us_data/tests/test_local_area_calibration/test_geo_masking.py new file mode 100644 index 00000000..b086efbf --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/test_geo_masking.py @@ -0,0 +1,199 @@ +"""Test geographic masking behavior in sparse matrix.""" + +import pytest +import numpy as np + + +def test_state_level_zero_masking( + X_sparse, targets_df, tracer, test_cds, n_households +): + """ + State-level targets have zeros for wrong-state CD columns. + + For a target with geographic_id=37 (NC), columns for CDs in other states + (HI, MT, AK) should all be zero. + """ + seed = 42 + rng = np.random.default_rng(seed) + n_hh = n_households + + state_targets = [] + for row_idx in range(len(targets_df)): + geo_id = targets_df.iloc[row_idx].get("geographic_id", "US") + if geo_id != "US": + try: + val = int(geo_id) + if val < 100: + state_targets.append((row_idx, val)) + except (ValueError, TypeError): + pass + + if not state_targets: + pytest.skip("No state-level targets found") + + errors = [] + checked = 0 + sample_targets = rng.choice( + len(state_targets), min(20, len(state_targets)), replace=False + ) + + for idx in sample_targets: + row_idx, target_state = state_targets[idx] + other_state_cds = [ + (i, cd) + for i, cd in enumerate(test_cds) + if int(cd) // 100 != target_state + ] + if not other_state_cds: + continue + + sample_cds = rng.choice( + len(other_state_cds), min(5, len(other_state_cds)), replace=False + ) + for cd_sample_idx in sample_cds: + cd_idx, cd = other_state_cds[cd_sample_idx] + sample_hh = rng.choice(n_hh, min(5, n_hh), replace=False) + for hh_idx in sample_hh: + col_idx = cd_idx * n_hh + hh_idx + actual = X_sparse[row_idx, col_idx] + checked += 1 + if actual != 0: + errors.append( + {"row": row_idx, "cd": cd, "value": float(actual)} + ) + + assert ( + not errors + ), f"State-level masking failed: {len(errors)}/{checked} should be zero" + + +def test_cd_level_zero_masking( + X_sparse, targets_df, tracer, test_cds, n_households +): + """ + CD-level targets have zeros for other CDs, even same-state. + + For a target with geographic_id=3707, columns for CDs 3701-3706, 3708-3714 + should all be zero, even though they're all in NC (state 37). + """ + seed = 42 + rng = np.random.default_rng(seed) + n_hh = n_households + + cd_targets_with_same_state = [] + for row_idx in range(len(targets_df)): + geo_id = targets_df.iloc[row_idx].get("geographic_id", "US") + if geo_id != "US": + try: + val = int(geo_id) + if val >= 100: + target_state = val // 100 + same_state_other_cds = [ + cd + for cd in test_cds + if int(cd) // 100 == target_state and cd != geo_id + ] + if same_state_other_cds: + cd_targets_with_same_state.append( + (row_idx, geo_id, same_state_other_cds) + ) + except (ValueError, TypeError): + pass + + if not cd_targets_with_same_state: + pytest.skip( + "No CD-level targets with same-state other CDs in test_cds" + ) + + errors = [] + same_state_checks = 0 + + for row_idx, target_cd, other_cds in cd_targets_with_same_state[:10]: + for cd in other_cds: + cd_idx = test_cds.index(cd) + for hh_idx in rng.choice(n_hh, 3, replace=False): + col_idx = cd_idx * n_hh + hh_idx + actual = X_sparse[row_idx, col_idx] + same_state_checks += 1 + if actual != 0: + errors.append( + { + "target_cd": target_cd, + "other_cd": cd, + "value": float(actual), + } + ) + + assert not errors, ( + f"CD-level masking failed: {len(errors)} same-state-different-CD " + f"non-zero values. First 5: {errors[:5]}" + ) + + +def test_national_no_geo_masking( + X_sparse, targets_df, tracer, sim, test_cds, dataset_path, n_households +): + """ + National targets have no geographic masking. + + National targets (geographic_id='US') can have non-zero values for ANY CD. + Values differ by destination state because benefits are recalculated + under each state's rules. + """ + seed = 42 + rng = np.random.default_rng(seed) + n_hh = n_households + hh_ids = tracer.original_household_ids + + national_rows = [ + i + for i in range(len(targets_df)) + if targets_df.iloc[i].get("geographic_id", "US") == "US" + ] + + if not national_rows: + pytest.skip("No national targets found") + + states_in_test = sorted(set(int(cd) // 100 for cd in test_cds)) + cds_by_state = { + state: [cd for cd in test_cds if int(cd) // 100 == state] + for state in states_in_test + } + + for row_idx in national_rows: + variable = targets_df.iloc[row_idx]["variable"] + + row_data = X_sparse.getrow(row_idx) + nonzero_cols = row_data.nonzero()[1] + + assert ( + len(nonzero_cols) > 0 + ), f"National target row {row_idx} ({variable}) has no non-zero values" + + sample_cols = rng.choice( + nonzero_cols, min(5, len(nonzero_cols)), replace=False + ) + + households_checked = 0 + households_with_multi_state_values = 0 + + for col_idx in sample_cols: + hh_idx = col_idx % n_hh + + values_by_state = {} + for state, cds in cds_by_state.items(): + cd = cds[0] + cd_idx = test_cds.index(cd) + state_col = cd_idx * n_hh + hh_idx + val = float(X_sparse[row_idx, state_col]) + if val != 0: + values_by_state[state] = val + + households_checked += 1 + if len(values_by_state) > 1: + households_with_multi_state_values += 1 + + assert households_with_multi_state_values > 0, ( + f"National target {variable}: no households have values in " + f"multiple states" + ) diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_same_state.py b/policyengine_us_data/tests/test_local_area_calibration/test_same_state.py new file mode 100644 index 00000000..a13f459d --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/test_same_state.py @@ -0,0 +1,99 @@ +"""Test same-state values match fresh simulations.""" + +import pytest +import numpy as np + +from policyengine_us import Microsimulation +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_calculated_variables, +) + + +def test_same_state_matches_original( + X_sparse, + targets_df, + tracer, + sim, + test_cds, + dataset_path, + n_households, + household_ids, + household_states, +): + """ + Same-state non-zero cells must match fresh same-state simulation. + + When household stays in same state, X_sparse should contain the value + calculated from a fresh simulation with state_fips set to that state. + """ + n_samples = 200 + seed = 42 + rng = np.random.default_rng(seed) + n_hh = n_households + hh_ids = household_ids + hh_states = household_states + + state_sims = {} + + def get_state_sim(state): + if state not in state_sims: + s = Microsimulation(dataset=dataset_path) + s.set_input( + "state_fips", 2023, np.full(n_hh, state, dtype=np.int32) + ) + for var in get_calculated_variables(s): + s.delete_arrays(var) + state_sims[state] = s + return state_sims[state] + + nonzero_rows, nonzero_cols = X_sparse.nonzero() + + same_state_indices = [] + for i in range(len(nonzero_rows)): + col_idx = nonzero_cols[i] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + orig_state = int(hh_states[hh_idx]) + if dest_state == orig_state: + same_state_indices.append(i) + + if not same_state_indices: + pytest.skip("No same-state non-zero cells found") + + sample_idx = rng.choice( + same_state_indices, + min(n_samples, len(same_state_indices)), + replace=False, + ) + errors = [] + + for idx in sample_idx: + row_idx = nonzero_rows[idx] + col_idx = nonzero_cols[idx] + cd_idx = col_idx // n_hh + hh_idx = col_idx % n_hh + cd = test_cds[cd_idx] + dest_state = int(cd) // 100 + variable = targets_df.iloc[row_idx]["variable"] + actual = float(X_sparse[row_idx, col_idx]) + state_sim = get_state_sim(dest_state) + expected = float( + state_sim.calculate(variable, map_to="household").values[hh_idx] + ) + + if not np.isclose(actual, expected, atol=0.5): + errors.append( + { + "hh_id": hh_ids[hh_idx], + "variable": variable, + "actual": actual, + "expected": expected, + } + ) + + assert not errors, ( + f"Same-state verification failed: {len(errors)}/{len(sample_idx)} " + f"mismatches. First 5: {errors[:5]}" + ) From 82691681e9998e76e3df9635d7372b3700c7cbca Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 5 Dec 2025 17:59:22 -0500 Subject: [PATCH 04/24] Rename GEO_STACKING to LOCAL_AREA_CALIBRATION and restore tracer functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename GEO_STACKING to LOCAL_AREA_CALIBRATION in cps.py, puf.py, extended_cps.py - Rename data-geo to data-local-area in Makefile and workflow - Add create_target_groups function to calibration_utils.py - Enhance MatrixTracer with get_group_rows method and variable_desc in row catalog - Add TARGET GROUPS section to print_matrix_structure output - Add local_area_calibration_setup.ipynb documentation notebook 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/reusable_test.yaml | 12 +- Makefile | 8 +- changelog_entry.yaml | 4 +- docs/local_area_calibration_setup.ipynb | 602 ++++++++++++++++++ docs/myst.yml | 1 + policyengine_us_data/datasets/cps/cps.py | 5 +- .../datasets/cps/extended_cps.py | 6 +- .../calibration_utils.py | 107 +++- .../local_area_calibration/matrix_tracer.py | 81 ++- policyengine_us_data/datasets/puf/puf.py | 4 +- 10 files changed, 804 insertions(+), 26 deletions(-) create mode 100644 docs/local_area_calibration_setup.ipynb diff --git a/.github/workflows/reusable_test.yaml b/.github/workflows/reusable_test.yaml index 4618f775..c439be4e 100644 --- a/.github/workflows/reusable_test.yaml +++ b/.github/workflows/reusable_test.yaml @@ -75,17 +75,17 @@ jobs: TEST_LITE: ${{ !inputs.upload_data }} PYTHON_LOG_LEVEL: INFO - - name: Build geo-stacking datasets + - name: Build datasets for local area calibration if: inputs.full_suite run: | - GEO_STACKING=true python policyengine_us_data/datasets/cps/cps.py - GEO_STACKING=true python policyengine_us_data/datasets/puf/puf.py - GEO_STACKING_MODE=true python policyengine_us_data/datasets/cps/extended_cps.py + LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/cps.py + LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/puf/puf.py + LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/extended_cps.py python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 - - name: Run sparse matrix builder tests + - name: Run local area calibration tests if: inputs.full_suite - run: pytest policyengine_us_data/datasets/cps/local_area_calibration/test_sparse_matrix_builder.py -v + run: pytest policyengine_us_data/tests/test_local_area_calibration/ -v - name: Save calibration log if: inputs.full_suite diff --git a/Makefile b/Makefile index d8b127b0..fde07f6b 100644 --- a/Makefile +++ b/Makefile @@ -74,10 +74,10 @@ data: mv policyengine_us_data/storage/enhanced_cps_2024.h5 policyengine_us_data/storage/dense_enhanced_cps_2024.h5 cp policyengine_us_data/storage/sparse_enhanced_cps_2024.h5 policyengine_us_data/storage/enhanced_cps_2024.h5 -data-geo: data - GEO_STACKING=true python policyengine_us_data/datasets/cps/cps.py - GEO_STACKING=true python policyengine_us_data/datasets/puf/puf.py - GEO_STACKING_MODE=true python policyengine_us_data/datasets/cps/extended_cps.py +data-local-area: data + LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/cps.py + LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/puf/puf.py + LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/extended_cps.py python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 clean: diff --git a/changelog_entry.yaml b/changelog_entry.yaml index 84dc236e..8b0c9961 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -2,7 +2,7 @@ changes: added: - Sparse matrix builder for local area calibration with database-driven constraints - - Geo-stacking data pipeline (make data-geo) for congressional district calibration + - Local area calibration data pipeline (make data-local-area) - ExtendedCPS_2023 and PUF_2023 dataset classes - Stratified CPS sampling to preserve high-income households - - Matrix verification tests for geo-stacking calibration + - Matrix verification tests for local area calibration diff --git a/docs/local_area_calibration_setup.ipynb b/docs/local_area_calibration_setup.ipynb new file mode 100644 index 00000000..9891673c --- /dev/null +++ b/docs/local_area_calibration_setup.ipynb @@ -0,0 +1,602 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-0", + "metadata": {}, + "source": [ + "# Local Area Calibration Setup\n", + "\n", + "This notebook demonstrates the sparse matrix construction for local area (congressional district) calibration. It uses a subset of CDs from NC, HI, MT, and AK for manageable runtime." + ] + }, + { + "cell_type": "markdown", + "id": "cell-1", + "metadata": {}, + "source": [ + "## Section 1: Setup & Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/baogorek/envs/sep/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TEST_LITE == False\n" + ] + } + ], + "source": [ + "from sqlalchemy import create_engine, text\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "from policyengine_us import Microsimulation\n", + "from policyengine_us_data.storage import STORAGE_FOLDER\n", + "from policyengine_us_data.datasets.cps.local_area_calibration.sparse_matrix_builder import (\n", + " SparseMatrixBuilder,\n", + ")\n", + "from policyengine_us_data.datasets.cps.local_area_calibration.matrix_tracer import (\n", + " MatrixTracer,\n", + ")\n", + "from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import (\n", + " get_calculated_variables,\n", + " create_target_groups,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-3", + "metadata": {}, + "outputs": [], + "source": [ + "db_path = STORAGE_FOLDER / \"policy_data.db\"\n", + "db_uri = f\"sqlite:///{db_path}\"\n", + "dataset_path = str(STORAGE_FOLDER / \"stratified_extended_cps_2023.h5\")\n", + "\n", + "engine = create_engine(db_uri)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-4", + "metadata": {}, + "source": [ + "## Section 2: Select Test Congressional Districts\n", + "\n", + "We use CDs from 4 states for testing:\n", + "- **NC (37)**: 14 CDs (3701-3714) - provides same-state different-CD test cases\n", + "- **HI (15)**: 2 CDs (1501-1502)\n", + "- **MT (30)**: 2 CDs (3001-3002)\n", + "- **AK (2)**: 1 CD (200)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing with 19 congressional districts:\n", + " NC (37): ['3701', '3702', '3703', '3704', '3705', '3706', '3707', '3708', '3709', '3710', '3711', '3712', '3713', '3714']\n", + " HI (15): ['1501', '1502']\n", + " MT (30): ['3001', '3002']\n", + " AK (2): ['201']\n" + ] + } + ], + "source": [ + "query = \"\"\"\n", + "SELECT DISTINCT sc.value as cd_geoid\n", + "FROM strata s\n", + "JOIN stratum_constraints sc ON s.stratum_id = sc.stratum_id\n", + "WHERE s.stratum_group_id = 1\n", + " AND sc.constraint_variable = 'congressional_district_geoid'\n", + " AND (\n", + " sc.value LIKE '37__'\n", + " OR sc.value LIKE '150_'\n", + " OR sc.value LIKE '300_'\n", + " OR sc.value = '200' OR sc.value = '201'\n", + " )\n", + "ORDER BY sc.value\n", + "\"\"\"\n", + "\n", + "with engine.connect() as conn:\n", + " result = conn.execute(text(query)).fetchall()\n", + " test_cds = [row[0] for row in result]\n", + "\n", + "print(f\"Testing with {len(test_cds)} congressional districts:\")\n", + "print(f\" NC (37): {[cd for cd in test_cds if cd.startswith('37')]}\")\n", + "print(f\" HI (15): {[cd for cd in test_cds if cd.startswith('15')]}\")\n", + "print(f\" MT (30): {[cd for cd in test_cds if cd.startswith('30')]}\")\n", + "print(f\" AK (2): {[cd for cd in test_cds if cd.startswith('20')]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-6", + "metadata": {}, + "source": [ + "## Section 3: Build the Sparse Matrix\n", + "\n", + "The sparse matrix `X_sparse` has:\n", + "- **Rows**: Calibration targets (e.g., SNAP totals by geography)\n", + "- **Columns**: (household × CD) pairs - each household appears once per CD\n", + "\n", + "We filter to SNAP targets only (stratum_group_id=4) for this demonstration." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "X_sparse shape: (539, 256652)\n", + " Rows (targets): 539\n", + " Columns (household × CD pairs): 256652\n", + " Non-zero entries: 67,668\n", + " Sparsity: 99.95%\n" + ] + } + ], + "source": [ + "sim = Microsimulation(dataset=dataset_path)\n", + "\n", + "builder = SparseMatrixBuilder(\n", + " db_uri,\n", + " time_period=2023,\n", + " cds_to_calibrate=test_cds,\n", + " dataset_path=dataset_path,\n", + ")\n", + "\n", + "targets_df, X_sparse, household_id_mapping = builder.build_matrix(\n", + " sim, target_filter={\"stratum_group_ids\": [4], \"variables\": [\"snap\"]}\n", + ")\n", + "\n", + "print(f\"X_sparse shape: {X_sparse.shape}\")\n", + "print(f\" Rows (targets): {X_sparse.shape[0]}\")\n", + "print(f\" Columns (household × CD pairs): {X_sparse.shape[1]}\")\n", + "print(f\" Non-zero entries: {X_sparse.nnz:,}\")\n", + "print(f\" Sparsity: {1 - X_sparse.nnz / (X_sparse.shape[0] * X_sparse.shape[1]):.2%}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-8", + "metadata": {}, + "source": [ + "## Section 4: Understanding the Matrix Structure with MatrixTracer\n", + "\n", + "The `MatrixTracer` helps navigate the sparse matrix by providing lookups between:\n", + "- Column indices ↔ (household_id, CD) pairs\n", + "- Row indices ↔ target definitions" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================================================================\n", + "MATRIX STRUCTURE BREAKDOWN\n", + "================================================================================\n", + "\n", + "Matrix dimensions: 539 rows x 256652 columns\n", + " Rows = 539 targets\n", + " Columns = 13508 households x 19 CDs\n", + " = 13,508 x 19 = 256,652\n", + "\n", + "--------------------------------------------------------------------------------\n", + "COLUMN STRUCTURE (Households stacked by CD)\n", + "--------------------------------------------------------------------------------\n", + "\n", + "Showing first and last 5 CDs of 19 total:\n", + "\n", + "First 5 CDs:\n", + "cd_geoid start_col end_col n_households\n", + " 1501 0 13507 13508\n", + " 1502 13508 27015 13508\n", + " 201 27016 40523 13508\n", + " 3001 40524 54031 13508\n", + " 3002 54032 67539 13508\n", + "\n", + "Last 5 CDs:\n", + "cd_geoid start_col end_col n_households\n", + " 3710 189112 202619 13508\n", + " 3711 202620 216127 13508\n", + " 3712 216128 229635 13508\n", + " 3713 229636 243143 13508\n", + " 3714 243144 256651 13508\n", + "\n", + "--------------------------------------------------------------------------------\n", + "ROW STRUCTURE (Targets)\n", + "--------------------------------------------------------------------------------\n", + "\n", + "Total targets: 539\n", + "\n", + "Targets by stratum group:\n", + " n_targets n_unique_vars\n", + "stratum_group_id \n", + "1 1 1\n", + "4 538 2\n", + "\n", + "--------------------------------------------------------------------------------\n", + "TARGET GROUPS (for loss calculation)\n", + "--------------------------------------------------------------------------------\n", + "\n", + "=== Creating Target Groups ===\n", + "\n", + "National targets:\n", + " Group 0: Snap = 107,062,860,000\n", + "\n", + "State targets:\n", + " Group 1: SNAP Household Count (51 targets)\n", + " Group 2: Snap (51 targets)\n", + "\n", + "District targets:\n", + " Group 3: SNAP Household Count (436 targets)\n", + "\n", + "Total groups created: 4\n", + "========================================\n", + " Group 0: National Snap (1 target, value=107,062,860,000) - rows [0]\n", + " Group 1: State SNAP Household Count (51 targets) - rows [1, 2, 3, ..., 50, 51]\n", + " Group 2: State Snap (51 targets) - rows [52, 53, 54, ..., 101, 102]\n", + " Group 3: District SNAP Household Count (436 targets) - rows [103, 104, 105, ..., 537, 538]\n", + "\n", + "================================================================================\n" + ] + } + ], + "source": [ + "tracer = MatrixTracer(\n", + " targets_df, X_sparse, household_id_mapping, test_cds, sim\n", + ")\n", + "\n", + "tracer.print_matrix_structure()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-11", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=== Creating Target Groups ===\n", + "\n", + "National targets:\n", + " Group 0: Snap = 107,062,860,000\n", + "\n", + "State targets:\n", + " Group 1: SNAP Household Count (51 targets)\n", + " Group 2: Snap (51 targets)\n", + "\n", + "District targets:\n", + " Group 3: SNAP Household Count (436 targets)\n", + "\n", + "Total groups created: 4\n", + "========================================\n" + ] + } + ], + "source": [ + "target_groups, group_info = create_target_groups(targets_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7e75756b-a317-4800-bac5-e0fd6bc43b8c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Row info for North Carolina's SNAP benefit amount:\n", + "{'row_index': 80, 'variable': 'snap', 'variable_desc': 'SNAP allotment', 'geographic_id': '37', 'target_value': 4041086120.0, 'stratum_id': 9799, 'stratum_group_id': 4}\n" + ] + } + ], + "source": [ + "target_group = tracer.get_group_rows(2)\n", + "row_loc = target_group.iloc[28]['row_index'] # Manually found the index value 28\n", + "row_info = tracer.get_row_info(row_loc)\n", + "var = row_info['variable']\n", + "var_desc = row_info['variable_desc']\n", + "target_geo_id = int(row_info['geographic_id'])\n", + "\n", + "print(\"Row info for North Carolina's SNAP benefit amount:\")\n", + "print(row_info)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c2be9721-ff11-4f78-ba0b-03407201dd53", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " household_id household_weight state_fips snap\n", + "0 25 1229.699951 23 789.199951\n", + "1 76 3119.330078 23 0.000000\n", + "2 92 1368.089966 23 0.000000\n", + "3 110 1457.579956 23 0.000000\n", + "4 140 1445.209961 23 0.000000\n", + "... ... ... ... ...\n", + "13503 178916 0.000000 15 0.000000\n", + "13504 178918 0.000000 15 0.000000\n", + "13505 178926 0.000000 15 0.000000\n", + "13506 178929 0.000000 15 0.000000\n", + "13507 178945 0.000000 15 0.000000\n", + "\n", + "[13508 rows x 4 columns]\n" + ] + } + ], + "source": [ + "hh_snap_df = pd.DataFrame(sim.calculate_dataframe([\n", + " \"household_id\", \"household_weight\", \"state_fips\", \"snap\"]) \n", + ")\n", + "print(hh_snap_df)" + ] + }, + { + "cell_type": "markdown", + "id": "438828ac-df94-4d3e-a9a8-227bb6f64933", + "metadata": {}, + "source": [ + "If we were to include `congressional_district_geoid` above, they would all be zeros. It's not until we do the calibration, i.e., come back with a vector of weights `w` to multiply `X_sparse` with, that we will set `congressional_district_geoid`.\n", + "\n", + "However, every household is already a donor to every contressional district. You can get the column positions for every household (remember targets are on the rows, donor households on the columns) by running tracer's get_household_column_positions with the *original* `household_id`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-12", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " household_id household_weight state_fips snap\n", + "0 25 1229.699951 23 789.199951\n", + "\n", + "Evaluating the tracer.get_household_column_positions dictionary:\n", + "\n", + "{'1501': 0, '1502': 13508, '201': 27016, '3001': 40524, '3002': 54032, '3701': 67540, '3702': 81048, '3703': 94556, '3704': 108064, '3705': 121572, '3706': 135080, '3707': 148588, '3708': 162096, '3709': 175604, '3710': 189112, '3711': 202620, '3712': 216128, '3713': 229636, '3714': 243144}\n" + ] + } + ], + "source": [ + "# Reverse lookup: get all column positions for a specific household\n", + "hh_id = hh_snap_df.loc[hh_snap_df.snap > 0].household_id.values[0]\n", + "print(hh_snap_df.loc[hh_snap_df.household_id == hh_id])\n", + "\n", + "print(\"\\nEvaluating the tracer.get_household_column_positions dictionary:\\n\")\n", + "positions = tracer.get_household_column_positions(hh_id)\n", + "print(positions)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "## Section 5: Understanding the cells of the X_Sparse matrix and Target vector" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e05aaeab-3786-4ff0-a50b-34577065d2e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Remember, this is a North Carolina target:\n", + "\n", + "target_id 9372\n", + "stratum_id 9799\n", + "variable snap\n", + "value 4041086120.0\n", + "period 2023\n", + "stratum_group_id 4\n", + "geographic_id 37\n", + "Name: 80, dtype: object\n", + "\n", + "Household donated to NC's 2nd district, 2023 SNAP dollars:\n", + "789.19995\n", + "\n", + "Household donated to NC's 2nd district, 2023 SNAP dollars:\n", + "0.0\n" + ] + } + ], + "source": [ + "print(\"Remember, this is a North Carolina target:\\n\")\n", + "print(targets_df.iloc[row_loc])\n", + "\n", + "print(\"\\nHousehold donated to NC's 2nd district, 2023 SNAP dollars:\")\n", + "print(X_sparse[row_loc, positions['3702']]) # Household donated to NC's 2nd district\n", + "\n", + "print(\"\\nHousehold donated to NC's 2nd district, 2023 SNAP dollars:\")\n", + "print(X_sparse[row_loc, positions['201']]) # Household donated to AK's at Large District" + ] + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": [ + "Key property: For state-level targets, only CDs in that state should have non-zero values.\n", + "\n", + "Example: A NC state SNAP target should have zeros for HI, MT, and AK CD columns.\n", + "\n", + "So let's see that same household's value for the Alaska state target:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8cdc264c-8335-40eb-afd9-4c4d023ec303", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Row info for Alaska's SNAP benefit amount:\n", + "{'row_index': 80, 'variable': 'snap', 'variable_desc': 'SNAP allotment', 'geographic_id': '37', 'target_value': 4041086120.0, 'stratum_id': 9799, 'stratum_group_id': 4}\n" + ] + } + ], + "source": [ + "target_group = tracer.get_group_rows(2)\n", + "new_row_loc = target_group.iloc[10]['row_index'] # Manually found the index value 10\n", + "row_info = tracer.get_row_info(row_loc)\n", + "var = row_info['variable']\n", + "var_desc = row_info['variable_desc']\n", + "target_geo_id = int(row_info['geographic_id'])\n", + "\n", + "print(\"Row info for Alaska's SNAP benefit amount:\")\n", + "print(row_info)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ac59b6f1-859f-4246-8a05-8cb26384c882", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Household donated to AK's 1st district, 2023 SNAP dollars:\n", + "342.48004\n" + ] + } + ], + "source": [ + "print(\"\\nHousehold donated to AK's 1st district, 2023 SNAP dollars:\")\n", + "print(X_sparse[new_row_loc, positions['201']]) # Household donated to AK's at Large District" + ] + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": [ + "## Section 6: Simulating State-Swapped Calculations\n", + "\n", + "When a household is \"transplanted\" to a different state, state-dependent benefits like SNAP are recalculated under the destination state's rules." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cell-19", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SNAP values for first 5 households under different state rules:\n", + " NC rules: [789.19995117 0. 0. 0. 0. ]\n", + " AK rules: [342.4800415 0. 0. 0. 0. ]\n", + " Difference: [-446.71990967 0. 0. 0. 0. ]\n" + ] + } + ], + "source": [ + "def create_state_simulation(state_fips):\n", + " \"\"\"Create a simulation with all households assigned to a specific state.\"\"\"\n", + " s = Microsimulation(dataset=dataset_path)\n", + " s.set_input(\n", + " \"state_fips\", 2023, np.full(hh_snap_df.shape[0], state_fips, dtype=np.int32)\n", + " )\n", + " for var in get_calculated_variables(s):\n", + " s.delete_arrays(var)\n", + " return s\n", + "\n", + "# Compare SNAP for first 5 households under NC vs AK rules\n", + "nc_sim = create_state_simulation(37) # NC\n", + "ak_sim = create_state_simulation(2) # AK\n", + "\n", + "nc_snap = nc_sim.calculate(\"snap\", map_to=\"household\").values[:5]\n", + "ak_snap = ak_sim.calculate(\"snap\", map_to=\"household\").values[:5]\n", + "\n", + "print(\"SNAP values for first 5 households under different state rules:\")\n", + "print(f\" NC rules: {nc_snap}\")\n", + "print(f\" AK rules: {ak_snap}\")\n", + "print(f\" Difference: {ak_snap - nc_snap}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/myst.yml b/docs/myst.yml index a39b3cfb..6b6ceae6 100644 --- a/docs/myst.yml +++ b/docs/myst.yml @@ -24,6 +24,7 @@ project: - file: background.md - file: data.md - file: methodology.md + - file: local_area_calibration_setup.ipynb - file: long_term_projections.ipynb - file: discussion.md - file: conclusion.md diff --git a/policyengine_us_data/datasets/cps/cps.py b/policyengine_us_data/datasets/cps/cps.py index ac464632..06619d6e 100644 --- a/policyengine_us_data/datasets/cps/cps.py +++ b/policyengine_us_data/datasets/cps/cps.py @@ -2117,14 +2117,13 @@ class Pooled_3_Year_CPS_2023(PooledCPS): url = "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5" -geo_stacking = os.environ.get("GEO_STACKING") == "true" +local_area_calibration = os.environ.get("LOCAL_AREA_CALIBRATION") == "true" if __name__ == "__main__": if test_lite: - CPS_2023().generate() CPS_2024().generate() CPS_2025().generate() - elif geo_stacking: + elif local_area_calibration: CPS_2023_Full().generate() else: CPS_2021().generate() diff --git a/policyengine_us_data/datasets/cps/extended_cps.py b/policyengine_us_data/datasets/cps/extended_cps.py index dace9d5f..350aa642 100644 --- a/policyengine_us_data/datasets/cps/extended_cps.py +++ b/policyengine_us_data/datasets/cps/extended_cps.py @@ -339,11 +339,11 @@ class ExtendedCPS_2024(ExtendedCPS): if __name__ == "__main__": - geo_stacking_mode = ( - os.environ.get("GEO_STACKING_MODE", "").lower() == "true" + local_area_calibration = ( + os.environ.get("LOCAL_AREA_CALIBRATION", "").lower() == "true" ) - if geo_stacking_mode: + if local_area_calibration: ExtendedCPS_2023().generate() else: ExtendedCPS_2024().generate() diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index 663d5147..8d685498 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -2,8 +2,9 @@ Shared utilities for calibration scripts. """ -from typing import List +from typing import List, Tuple import numpy as np +import pandas as pd def get_calculated_variables(sim) -> List[str]: @@ -78,3 +79,107 @@ def _get_geo_level(geo_id) -> int: return 1 if val < 100 else 2 except (ValueError, TypeError): return 3 + + +def create_target_groups( + targets_df: pd.DataFrame, +) -> Tuple[np.ndarray, List[str]]: + """ + Automatically create target groups based on metadata. + + Grouping rules: + 1. Groups are ordered by geographic level: National -> State -> District + 2. Within each level, targets are grouped by variable type + 3. Each group contributes equally to the total loss + + Parameters + ---------- + targets_df : pd.DataFrame + DataFrame containing target metadata with columns: + - stratum_group_id: Identifier for the type of target + - geographic_id: Geographic identifier (US, state FIPS, CD GEOID) + - variable: Variable name + - value: Target value + + Returns + ------- + target_groups : np.ndarray + Array of group IDs for each target + group_info : List[str] + List of descriptive strings for each group + """ + target_groups = np.zeros(len(targets_df), dtype=int) + group_id = 0 + group_info = [] + processed_mask = np.zeros(len(targets_df), dtype=bool) + + print("\n=== Creating Target Groups ===") + + # Add geo_level column for sorting + targets_df = targets_df.copy() + targets_df["_geo_level"] = targets_df["geographic_id"].apply( + _get_geo_level + ) + + geo_level_names = {0: "National", 1: "State", 2: "District"} + + # Process by geographic level: National (0) -> State (1) -> District (2) + for level in [0, 1, 2]: + level_mask = targets_df["_geo_level"] == level + if not level_mask.any(): + continue + + level_name = geo_level_names.get(level, f"Level {level}") + print(f"\n{level_name} targets:") + + # Get unique variables at this level + level_df = targets_df[level_mask & ~processed_mask] + unique_vars = sorted(level_df["variable"].unique()) + + for var_name in unique_vars: + var_mask = ( + (targets_df["variable"] == var_name) + & level_mask + & ~processed_mask + ) + + if not var_mask.any(): + continue + + matching = targets_df[var_mask] + n_targets = var_mask.sum() + + # Assign group + target_groups[var_mask] = group_id + processed_mask |= var_mask + + # Create descriptive label + stratum_group = matching["stratum_group_id"].iloc[0] + if var_name == "household_count" and stratum_group == 4: + label = "SNAP Household Count" + elif var_name == "snap": + label = "Snap" + else: + label = var_name.replace("_", " ").title() + + # Format output based on level and count + if n_targets == 1: + value = matching["value"].iloc[0] + info_str = ( + f"{level_name} {label} (1 target, value={value:,.0f})" + ) + print_str = f" Group {group_id}: {label} = {value:,.0f}" + else: + info_str = f"{level_name} {label} ({n_targets} targets)" + print_str = ( + f" Group {group_id}: {label} ({n_targets} targets)" + ) + + group_info.append(f"Group {group_id}: {info_str}") + print(print_str) + group_id += 1 + + print(f"\nTotal groups created: {group_id}") + print("=" * 40) + + return target_groups, group_info diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py b/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py index 8031422a..e7cbf57b 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/matrix_tracer.py @@ -42,6 +42,10 @@ from typing import Dict, List from scipy import sparse +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + create_target_groups, +) + logger = logging.getLogger(__name__) @@ -118,10 +122,17 @@ def _build_row_catalog(self) -> pd.DataFrame: catalog = [] for row_idx, (_, target) in enumerate(self.targets_df.iterrows()): + var_name = target["variable"] + var_desc = "" + if var_name in self.sim.tax_benefit_system.variables: + var_obj = self.sim.tax_benefit_system.variables[var_name] + var_desc = getattr(var_obj, "label", var_name) + catalog.append( { "row_index": row_idx, - "variable": target["variable"], + "variable": var_name, + "variable_desc": var_desc, "geographic_id": target.get("geographic_id", "unknown"), "target_value": target["value"], "stratum_id": target.get("stratum_id"), @@ -201,21 +212,24 @@ def get_household_column_positions( return positions - def print_matrix_structure(self): + def print_matrix_structure(self, show_groups=True): """Print a comprehensive breakdown of the matrix structure.""" print("\n" + "=" * 80) print("MATRIX STRUCTURE BREAKDOWN") print("=" * 80) print( - f"\nMatrix dimensions: {self.matrix.shape[0]} rows x {self.matrix.shape[1]} columns" + f"\nMatrix dimensions: {self.matrix.shape[0]} rows x " + f"{self.matrix.shape[1]} columns" ) print(f" Rows = {len(self.row_catalog)} targets") print( - f" Columns = {self.n_households} households x {self.n_geographies} CDs" + f" Columns = {self.n_households} households x " + f"{self.n_geographies} CDs" ) print( - f" = {self.n_households:,} x {self.n_geographies} = {self.matrix.shape[1]:,}" + f" = {self.n_households:,} x {self.n_geographies} " + f"= {self.matrix.shape[1]:,}" ) print("\n" + "-" * 80) @@ -250,6 +264,17 @@ def print_matrix_structure(self): print("-" * 80) print(f"\nTotal targets: {len(self.row_catalog)}") + + # Summarize by geographic level if column exists + if "geographic_level" in self.row_catalog.columns: + print("\nTargets by geographic level:") + geo_level_summary = ( + self.row_catalog.groupby("geographic_level") + .size() + .reset_index(name="n_targets") + ) + print(geo_level_summary.to_string(index=False)) + print("\nTargets by stratum group:") stratum_summary = ( self.row_catalog.groupby("stratum_group_id") @@ -260,6 +285,34 @@ def print_matrix_structure(self): ) print(stratum_summary.to_string()) + # Create and display target groups with row indices + if show_groups: + print("\n" + "-" * 80) + print("TARGET GROUPS (for loss calculation)") + print("-" * 80) + + target_groups, group_info = create_target_groups(self.targets_df) + + # Store for later use + self.target_groups = target_groups + + # Print each group with row indices + for group_id, info in enumerate(group_info): + group_mask = target_groups == group_id + row_indices = np.where(group_mask)[0] + + # Format row indices for display + if len(row_indices) > 6: + row_display = ( + f"[{row_indices[0]}, {row_indices[1]}, " + f"{row_indices[2]}, ..., {row_indices[-2]}, " + f"{row_indices[-1]}]" + ) + else: + row_display = str(row_indices.tolist()) + + print(f" {info} - rows {row_display}") + print("\n" + "=" * 80) def print_column_catalog(self, max_rows: int = 50): @@ -276,6 +329,24 @@ def print_row_catalog(self, max_rows: int = 50): ) print(self.row_catalog.head(max_rows).to_string(index=False)) + def get_group_rows(self, group_id: int) -> pd.DataFrame: + """ + Get all rows belonging to a specific target group. + + Args: + group_id: The group ID to filter by + + Returns: + DataFrame of row catalog entries for this group + """ + if not hasattr(self, "target_groups"): + self.target_groups, self.group_info = create_target_groups( + self.targets_df + ) + + group_mask = self.target_groups == group_id + return self.row_catalog[group_mask].copy() + def trace_household_targets(self, original_hh_id: int) -> pd.DataFrame: """ Extract all target values for a household across all geographies. diff --git a/policyengine_us_data/datasets/puf/puf.py b/policyengine_us_data/datasets/puf/puf.py index 3cb72efa..9d605aca 100644 --- a/policyengine_us_data/datasets/puf/puf.py +++ b/policyengine_us_data/datasets/puf/puf.py @@ -757,9 +757,9 @@ class PUF_2024(PUF): if __name__ == "__main__": import os - geo_stacking = os.environ.get("GEO_STACKING") == "true" + local_area_calibration = os.environ.get("LOCAL_AREA_CALIBRATION") == "true" - if geo_stacking: + if local_area_calibration: PUF_2023().generate() else: PUF_2015().generate() From 95f21cab2b80dd8f7f000684c00d10ea87d61c46 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 5 Dec 2025 18:42:11 -0500 Subject: [PATCH 05/24] Clear notebook outputs for Myst compatibility --- docs/local_area_calibration_setup.ipynb | 293 +++--------------------- 1 file changed, 30 insertions(+), 263 deletions(-) diff --git a/docs/local_area_calibration_setup.ipynb b/docs/local_area_calibration_setup.ipynb index 9891673c..e8eae899 100644 --- a/docs/local_area_calibration_setup.ipynb +++ b/docs/local_area_calibration_setup.ipynb @@ -20,26 +20,10 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "cell-2", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/baogorek/envs/sep/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TEST_LITE == False\n" - ] - } - ], + "outputs": [], "source": [ "from sqlalchemy import create_engine, text\n", "import pandas as pd\n", @@ -61,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "cell-3", "metadata": {}, "outputs": [], @@ -89,22 +73,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "cell-5", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Testing with 19 congressional districts:\n", - " NC (37): ['3701', '3702', '3703', '3704', '3705', '3706', '3707', '3708', '3709', '3710', '3711', '3712', '3713', '3714']\n", - " HI (15): ['1501', '1502']\n", - " MT (30): ['3001', '3002']\n", - " AK (2): ['201']\n" - ] - } - ], + "outputs": [], "source": [ "query = \"\"\"\n", "SELECT DISTINCT sc.value as cd_geoid\n", @@ -141,29 +113,17 @@ "\n", "The sparse matrix `X_sparse` has:\n", "- **Rows**: Calibration targets (e.g., SNAP totals by geography)\n", - "- **Columns**: (household × CD) pairs - each household appears once per CD\n", + "- **Columns**: (household \u00d7 CD) pairs - each household appears once per CD\n", "\n", "We filter to SNAP targets only (stratum_group_id=4) for this demonstration." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "cell-7", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "X_sparse shape: (539, 256652)\n", - " Rows (targets): 539\n", - " Columns (household × CD pairs): 256652\n", - " Non-zero entries: 67,668\n", - " Sparsity: 99.95%\n" - ] - } - ], + "outputs": [], "source": [ "sim = Microsimulation(dataset=dataset_path)\n", "\n", @@ -180,7 +140,7 @@ "\n", "print(f\"X_sparse shape: {X_sparse.shape}\")\n", "print(f\" Rows (targets): {X_sparse.shape[0]}\")\n", - "print(f\" Columns (household × CD pairs): {X_sparse.shape[1]}\")\n", + "print(f\" Columns (household \u00d7 CD pairs): {X_sparse.shape[1]}\")\n", "print(f\" Non-zero entries: {X_sparse.nnz:,}\")\n", "print(f\" Sparsity: {1 - X_sparse.nnz / (X_sparse.shape[0] * X_sparse.shape[1]):.2%}\")" ] @@ -193,91 +153,16 @@ "## Section 4: Understanding the Matrix Structure with MatrixTracer\n", "\n", "The `MatrixTracer` helps navigate the sparse matrix by providing lookups between:\n", - "- Column indices ↔ (household_id, CD) pairs\n", - "- Row indices ↔ target definitions" + "- Column indices \u2194 (household_id, CD) pairs\n", + "- Row indices \u2194 target definitions" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "cell-9", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "================================================================================\n", - "MATRIX STRUCTURE BREAKDOWN\n", - "================================================================================\n", - "\n", - "Matrix dimensions: 539 rows x 256652 columns\n", - " Rows = 539 targets\n", - " Columns = 13508 households x 19 CDs\n", - " = 13,508 x 19 = 256,652\n", - "\n", - "--------------------------------------------------------------------------------\n", - "COLUMN STRUCTURE (Households stacked by CD)\n", - "--------------------------------------------------------------------------------\n", - "\n", - "Showing first and last 5 CDs of 19 total:\n", - "\n", - "First 5 CDs:\n", - "cd_geoid start_col end_col n_households\n", - " 1501 0 13507 13508\n", - " 1502 13508 27015 13508\n", - " 201 27016 40523 13508\n", - " 3001 40524 54031 13508\n", - " 3002 54032 67539 13508\n", - "\n", - "Last 5 CDs:\n", - "cd_geoid start_col end_col n_households\n", - " 3710 189112 202619 13508\n", - " 3711 202620 216127 13508\n", - " 3712 216128 229635 13508\n", - " 3713 229636 243143 13508\n", - " 3714 243144 256651 13508\n", - "\n", - "--------------------------------------------------------------------------------\n", - "ROW STRUCTURE (Targets)\n", - "--------------------------------------------------------------------------------\n", - "\n", - "Total targets: 539\n", - "\n", - "Targets by stratum group:\n", - " n_targets n_unique_vars\n", - "stratum_group_id \n", - "1 1 1\n", - "4 538 2\n", - "\n", - "--------------------------------------------------------------------------------\n", - "TARGET GROUPS (for loss calculation)\n", - "--------------------------------------------------------------------------------\n", - "\n", - "=== Creating Target Groups ===\n", - "\n", - "National targets:\n", - " Group 0: Snap = 107,062,860,000\n", - "\n", - "State targets:\n", - " Group 1: SNAP Household Count (51 targets)\n", - " Group 2: Snap (51 targets)\n", - "\n", - "District targets:\n", - " Group 3: SNAP Household Count (436 targets)\n", - "\n", - "Total groups created: 4\n", - "========================================\n", - " Group 0: National Snap (1 target, value=107,062,860,000) - rows [0]\n", - " Group 1: State SNAP Household Count (51 targets) - rows [1, 2, 3, ..., 50, 51]\n", - " Group 2: State Snap (51 targets) - rows [52, 53, 54, ..., 101, 102]\n", - " Group 3: District SNAP Household Count (436 targets) - rows [103, 104, 105, ..., 537, 538]\n", - "\n", - "================================================================================\n" - ] - } - ], + "outputs": [], "source": [ "tracer = MatrixTracer(\n", " targets_df, X_sparse, household_id_mapping, test_cds, sim\n", @@ -288,51 +173,20 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "cell-11", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "=== Creating Target Groups ===\n", - "\n", - "National targets:\n", - " Group 0: Snap = 107,062,860,000\n", - "\n", - "State targets:\n", - " Group 1: SNAP Household Count (51 targets)\n", - " Group 2: Snap (51 targets)\n", - "\n", - "District targets:\n", - " Group 3: SNAP Household Count (436 targets)\n", - "\n", - "Total groups created: 4\n", - "========================================\n" - ] - } - ], + "outputs": [], "source": [ "target_groups, group_info = create_target_groups(targets_df)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "7e75756b-a317-4800-bac5-e0fd6bc43b8c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Row info for North Carolina's SNAP benefit amount:\n", - "{'row_index': 80, 'variable': 'snap', 'variable_desc': 'SNAP allotment', 'geographic_id': '37', 'target_value': 4041086120.0, 'stratum_id': 9799, 'stratum_group_id': 4}\n" - ] - } - ], + "outputs": [], "source": [ "target_group = tracer.get_group_rows(2)\n", "row_loc = target_group.iloc[28]['row_index'] # Manually found the index value 28\n", @@ -347,31 +201,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "c2be9721-ff11-4f78-ba0b-03407201dd53", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " household_id household_weight state_fips snap\n", - "0 25 1229.699951 23 789.199951\n", - "1 76 3119.330078 23 0.000000\n", - "2 92 1368.089966 23 0.000000\n", - "3 110 1457.579956 23 0.000000\n", - "4 140 1445.209961 23 0.000000\n", - "... ... ... ... ...\n", - "13503 178916 0.000000 15 0.000000\n", - "13504 178918 0.000000 15 0.000000\n", - "13505 178926 0.000000 15 0.000000\n", - "13506 178929 0.000000 15 0.000000\n", - "13507 178945 0.000000 15 0.000000\n", - "\n", - "[13508 rows x 4 columns]\n" - ] - } - ], + "outputs": [], "source": [ "hh_snap_df = pd.DataFrame(sim.calculate_dataframe([\n", " \"household_id\", \"household_weight\", \"state_fips\", \"snap\"]) \n", @@ -391,23 +224,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "cell-12", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " household_id household_weight state_fips snap\n", - "0 25 1229.699951 23 789.199951\n", - "\n", - "Evaluating the tracer.get_household_column_positions dictionary:\n", - "\n", - "{'1501': 0, '1502': 13508, '201': 27016, '3001': 40524, '3002': 54032, '3701': 67540, '3702': 81048, '3703': 94556, '3704': 108064, '3705': 121572, '3706': 135080, '3707': 148588, '3708': 162096, '3709': 175604, '3710': 189112, '3711': 202620, '3712': 216128, '3713': 229636, '3714': 243144}\n" - ] - } - ], + "outputs": [], "source": [ "# Reverse lookup: get all column positions for a specific household\n", "hh_id = hh_snap_df.loc[hh_snap_df.snap > 0].household_id.values[0]\n", @@ -428,33 +248,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "e05aaeab-3786-4ff0-a50b-34577065d2e0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Remember, this is a North Carolina target:\n", - "\n", - "target_id 9372\n", - "stratum_id 9799\n", - "variable snap\n", - "value 4041086120.0\n", - "period 2023\n", - "stratum_group_id 4\n", - "geographic_id 37\n", - "Name: 80, dtype: object\n", - "\n", - "Household donated to NC's 2nd district, 2023 SNAP dollars:\n", - "789.19995\n", - "\n", - "Household donated to NC's 2nd district, 2023 SNAP dollars:\n", - "0.0\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Remember, this is a North Carolina target:\\n\")\n", "print(targets_df.iloc[row_loc])\n", @@ -480,19 +277,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "8cdc264c-8335-40eb-afd9-4c4d023ec303", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Row info for Alaska's SNAP benefit amount:\n", - "{'row_index': 80, 'variable': 'snap', 'variable_desc': 'SNAP allotment', 'geographic_id': '37', 'target_value': 4041086120.0, 'stratum_id': 9799, 'stratum_group_id': 4}\n" - ] - } - ], + "outputs": [], "source": [ "target_group = tracer.get_group_rows(2)\n", "new_row_loc = target_group.iloc[10]['row_index'] # Manually found the index value 10\n", @@ -507,20 +295,10 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "ac59b6f1-859f-4246-8a05-8cb26384c882", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Household donated to AK's 1st district, 2023 SNAP dollars:\n", - "342.48004\n" - ] - } - ], + "outputs": [], "source": [ "print(\"\\nHousehold donated to AK's 1st district, 2023 SNAP dollars:\")\n", "print(X_sparse[new_row_loc, positions['201']]) # Household donated to AK's at Large District" @@ -538,21 +316,10 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "cell-19", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SNAP values for first 5 households under different state rules:\n", - " NC rules: [789.19995117 0. 0. 0. 0. ]\n", - " AK rules: [342.4800415 0. 0. 0. 0. ]\n", - " Difference: [-446.71990967 0. 0. 0. 0. ]\n" - ] - } - ], + "outputs": [], "source": [ "def create_state_simulation(state_fips):\n", " \"\"\"Create a simulation with all households assigned to a specific state.\"\"\"\n", @@ -599,4 +366,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From 07852a112d8f8dfd0d6c5df356437b931202add4 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 5 Dec 2025 18:48:56 -0500 Subject: [PATCH 06/24] Pin mystmd>=1.7.0 to fix notebook rendering in docs --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3d00d389..095c5099 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dev = [ "tabulate", "furo", "jupyter-book", - "mystmd", + "mystmd>=1.7.0", "yaml-changelog>=0.1.7", "build", "tomli", From f5f16144029d653da774d2e1d5f649f786df1a04 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Tue, 9 Dec 2025 11:55:15 -0500 Subject: [PATCH 07/24] Add population-weighted P(county|CD) distributions and stacked dataset builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add make_county_cd_distributions.py to compute P(county|CD) from Census block data - Add county_cd_distributions.csv with distributions for all 436 CDs - Add county_assignment.py module for assigning counties to households - Add stacked_dataset_builder.py for creating CD-stacked H5 datasets - Add tests for county assignment functionality - Update calibration_utils.py with state/CD mapping utilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 2 + .../calibration_utils.py | 233 ++ .../county_assignment.py | 125 + .../stacked_dataset_builder.py | 829 ++++ .../make_county_cd_distributions.py | 193 + .../storage/county_cd_distributions.csv | 3720 +++++++++++++++++ .../test_county_assignment.py | 114 + 7 files changed, 5216 insertions(+) create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/county_assignment.py create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py create mode 100644 policyengine_us_data/storage/calibration_targets/make_county_cd_distributions.py create mode 100644 policyengine_us_data/storage/county_cd_distributions.csv create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_county_assignment.py diff --git a/changelog_entry.yaml b/changelog_entry.yaml index 8b0c9961..cf43c831 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -6,3 +6,5 @@ - ExtendedCPS_2023 and PUF_2023 dataset classes - Stratified CPS sampling to preserve high-income households - Matrix verification tests for local area calibration + - Population-weighted P(county|CD) distributions from Census block data + - County assignment module for stacked dataset builder diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index 8d685498..abf36057 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -6,6 +6,177 @@ import numpy as np import pandas as pd +from policyengine_us.variables.household.demographic.geographic.state_name import ( + StateName, +) +from policyengine_us.variables.household.demographic.geographic.state_code import ( + StateCode, +) + + +# State/Geographic Mappings +STATE_CODES = { + 1: "AL", + 2: "AK", + 4: "AZ", + 5: "AR", + 6: "CA", + 8: "CO", + 9: "CT", + 10: "DE", + 11: "DC", + 12: "FL", + 13: "GA", + 15: "HI", + 16: "ID", + 17: "IL", + 18: "IN", + 19: "IA", + 20: "KS", + 21: "KY", + 22: "LA", + 23: "ME", + 24: "MD", + 25: "MA", + 26: "MI", + 27: "MN", + 28: "MS", + 29: "MO", + 30: "MT", + 31: "NE", + 32: "NV", + 33: "NH", + 34: "NJ", + 35: "NM", + 36: "NY", + 37: "NC", + 38: "ND", + 39: "OH", + 40: "OK", + 41: "OR", + 42: "PA", + 44: "RI", + 45: "SC", + 46: "SD", + 47: "TN", + 48: "TX", + 49: "UT", + 50: "VT", + 51: "VA", + 53: "WA", + 54: "WV", + 55: "WI", + 56: "WY", +} + +STATE_FIPS_TO_NAME = { + 1: StateName.AL, + 2: StateName.AK, + 4: StateName.AZ, + 5: StateName.AR, + 6: StateName.CA, + 8: StateName.CO, + 9: StateName.CT, + 10: StateName.DE, + 11: StateName.DC, + 12: StateName.FL, + 13: StateName.GA, + 15: StateName.HI, + 16: StateName.ID, + 17: StateName.IL, + 18: StateName.IN, + 19: StateName.IA, + 20: StateName.KS, + 21: StateName.KY, + 22: StateName.LA, + 23: StateName.ME, + 24: StateName.MD, + 25: StateName.MA, + 26: StateName.MI, + 27: StateName.MN, + 28: StateName.MS, + 29: StateName.MO, + 30: StateName.MT, + 31: StateName.NE, + 32: StateName.NV, + 33: StateName.NH, + 34: StateName.NJ, + 35: StateName.NM, + 36: StateName.NY, + 37: StateName.NC, + 38: StateName.ND, + 39: StateName.OH, + 40: StateName.OK, + 41: StateName.OR, + 42: StateName.PA, + 44: StateName.RI, + 45: StateName.SC, + 46: StateName.SD, + 47: StateName.TN, + 48: StateName.TX, + 49: StateName.UT, + 50: StateName.VT, + 51: StateName.VA, + 53: StateName.WA, + 54: StateName.WV, + 55: StateName.WI, + 56: StateName.WY, +} + +STATE_FIPS_TO_CODE = { + 1: StateCode.AL, + 2: StateCode.AK, + 4: StateCode.AZ, + 5: StateCode.AR, + 6: StateCode.CA, + 8: StateCode.CO, + 9: StateCode.CT, + 10: StateCode.DE, + 11: StateCode.DC, + 12: StateCode.FL, + 13: StateCode.GA, + 15: StateCode.HI, + 16: StateCode.ID, + 17: StateCode.IL, + 18: StateCode.IN, + 19: StateCode.IA, + 20: StateCode.KS, + 21: StateCode.KY, + 22: StateCode.LA, + 23: StateCode.ME, + 24: StateCode.MD, + 25: StateCode.MA, + 26: StateCode.MI, + 27: StateCode.MN, + 28: StateCode.MS, + 29: StateCode.MO, + 30: StateCode.MT, + 31: StateCode.NE, + 32: StateCode.NV, + 33: StateCode.NH, + 34: StateCode.NJ, + 35: StateCode.NM, + 36: StateCode.NY, + 37: StateCode.NC, + 38: StateCode.ND, + 39: StateCode.OH, + 40: StateCode.OK, + 41: StateCode.OR, + 42: StateCode.PA, + 44: StateCode.RI, + 45: StateCode.SC, + 46: StateCode.SD, + 47: StateCode.TN, + 48: StateCode.TX, + 49: StateCode.UT, + 50: StateCode.VT, + 51: StateCode.VA, + 53: StateCode.WA, + 54: StateCode.WV, + 55: StateCode.WI, + 56: StateCode.WY, +} + def get_calculated_variables(sim) -> List[str]: """ @@ -183,3 +354,65 @@ def create_target_groups( print("=" * 40) return target_groups, group_info + + +def get_all_cds_from_database(db_uri: str) -> List[str]: + """ + Get ordered list of all CD GEOIDs from database. + + Args: + db_uri: SQLAlchemy database URI (e.g., "sqlite:///path/to/db") + + Returns: + List of CD GEOID strings ordered by value + """ + from sqlalchemy import create_engine, text + + engine = create_engine(db_uri) + query = """ + SELECT DISTINCT sc.value as cd_geoid + FROM strata s + JOIN stratum_constraints sc ON s.stratum_id = sc.stratum_id + WHERE s.stratum_group_id = 1 + AND sc.constraint_variable = 'congressional_district_geoid' + ORDER BY sc.value + """ + with engine.connect() as conn: + result = conn.execute(text(query)).fetchall() + return [row[0] for row in result] + + +def get_cd_index_mapping(db_uri: str = None): + """ + Get the canonical CD GEOID to index mapping. + + Args: + db_uri: SQLAlchemy database URI. If None, uses default db location. + + Returns: + tuple: (cd_to_index dict, index_to_cd dict, cds_ordered list) + """ + from sqlalchemy import create_engine, text + from pathlib import Path + from policyengine_us_data.storage import STORAGE_FOLDER + + if db_uri is None: + db_path = STORAGE_FOLDER / "calibration_targets" / "policy_data.db" + db_uri = f"sqlite:///{db_path}" + + engine = create_engine(db_uri) + query = """ + SELECT DISTINCT sc.value as cd_geoid + FROM strata s + JOIN stratum_constraints sc ON s.stratum_id = sc.stratum_id + WHERE s.stratum_group_id = 1 + AND sc.constraint_variable = "congressional_district_geoid" + ORDER BY sc.value + """ + with engine.connect() as conn: + result = conn.execute(text(query)).fetchall() + cds_ordered = [row[0] for row in result] + + cd_to_index = {cd: idx for idx, cd in enumerate(cds_ordered)} + index_to_cd = {idx: cd for idx, cd in enumerate(cds_ordered)} + return cd_to_index, index_to_cd, cds_ordered diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/county_assignment.py b/policyengine_us_data/datasets/cps/local_area_calibration/county_assignment.py new file mode 100644 index 00000000..a3b8c19e --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/county_assignment.py @@ -0,0 +1,125 @@ +""" +County assignment for congressional districts. + +Provides conditional probability distributions P(county | CD) for assigning +counties to households within each congressional district. + +The distributions are pre-computed from Census block-level data and stored +in storage/county_cd_distributions.csv. If the CSV is not available, falls +back to uniform distributions across all counties in the CD's state. +""" + +import random +from typing import Dict, List +import numpy as np +import pandas as pd + +from policyengine_us.variables.household.demographic.geographic.county.county_enum import ( + County, +) +from policyengine_us_data.storage import STORAGE_FOLDER + + +def _build_state_counties() -> Dict[str, List[str]]: + """Build mapping from state code to list of county enum names.""" + state_counties = {} + for name in County._member_names_: + if name == "UNKNOWN": + continue + state_code = name.split("_")[-1] + if state_code not in state_counties: + state_counties[state_code] = [] + state_counties[state_code].append(name) + return state_counties + + +_STATE_COUNTIES = _build_state_counties() + + +def _generate_uniform_distribution(cd_geoid: str) -> Dict[str, float]: + """Generate uniform distribution across counties in CD's state.""" + from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + STATE_CODES, + ) + + fips_to_code = {fips: code for fips, code in STATE_CODES.items()} + state_fips = int(cd_geoid) // 100 + state_code = fips_to_code.get(state_fips) + + if state_code and state_code in _STATE_COUNTIES: + counties = _STATE_COUNTIES[state_code] + prob = 1.0 / len(counties) + return {c: prob for c in counties} + return {"UNKNOWN": 1.0} + + +def _load_county_distributions() -> Dict[str, Dict[str, float]]: + """Load pre-computed P(county|CD) distributions from CSV.""" + csv_path = STORAGE_FOLDER / "county_cd_distributions.csv" + + if not csv_path.exists(): + print( + f"Warning: {csv_path} not found. " + "Using uniform distributions. " + "Run make_county_cd_distributions.py to generate." + ) + return {} + + df = pd.read_csv(csv_path) + distributions = {} + for cd_geoid, group in df.groupby("cd_geoid"): + distributions[str(int(cd_geoid))] = dict( + zip(group["county_name"], group["probability"]) + ) + return distributions + + +# Load distributions at module import +_CD_COUNTY_DISTRIBUTIONS = _load_county_distributions() + + +def get_county_index(county_name: str) -> int: + """Convert county enum name to integer index.""" + return County._member_names_.index(county_name) + + +def assign_counties_for_cd( + cd_geoid: str, + n_households: int, + seed: int, + distributions: Dict[str, Dict[str, float]] = None, +) -> np.ndarray: + """ + Assign county indices to households in a CD using weighted random selection. + + Uses pre-computed P(county|CD) distributions from Census block data. + Falls back to uniform distribution if pre-computed data unavailable. + + Args: + cd_geoid: Congressional district geoid (e.g., "3610") + n_households: Number of households to assign + seed: Random seed for reproducibility + distributions: Optional override distributions. If None, uses + pre-computed distributions from CSV. + + Returns: + Array of county enum indices (integers) + """ + random.seed(seed) + + # Use pre-computed distributions by default + if distributions is None: + distributions = _CD_COUNTY_DISTRIBUTIONS + + cd_key = str(int(cd_geoid)) + + if cd_key in distributions: + dist = distributions[cd_key] + else: + # Fall back to uniform distribution for this CD + dist = _generate_uniform_distribution(cd_key) + + counties = list(dist.keys()) + weights = list(dist.values()) + selected = random.choices(counties, weights=weights, k=n_households) + return np.array([get_county_index(c) for c in selected], dtype=np.int32) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py new file mode 100644 index 00000000..25cb1f8e --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -0,0 +1,829 @@ +""" +Create a sparse congressional district-stacked dataset with non-zero weight +households. +""" + +import os +import numpy as np +import pandas as pd +import h5py +from pathlib import Path +from policyengine_us import Microsimulation +from policyengine_core.data.dataset import Dataset +from policyengine_core.enums import Enum +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_all_cds_from_database, + get_calculated_variables, + STATE_CODES, + STATE_FIPS_TO_NAME, + STATE_FIPS_TO_CODE, +) +from policyengine_us.variables.household.demographic.geographic.county.county_enum import ( + County, +) +from policyengine_us_data.datasets.cps.local_area_calibration.county_assignment import ( + assign_counties_for_cd, +) + + +def create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=None, + output_path=None, + dataset_path=None, +): + """ + Create a SPARSE congressional district-stacked dataset using DataFrame approach. + + Args: + w: Calibrated weight vector from L0 calibration (length = n_households * n_cds) + cds_to_calibrate: List of CD GEOID codes used in calibration + cd_subset: Optional list of CD GEOIDs to include (subset of cds_to_calibrate) + output_path: Where to save the sparse CD-stacked h5 file + dataset_path: Path to the base .h5 dataset used to create the training matrices + """ + + # Handle CD subset filtering + if cd_subset is not None: + # Validate that requested CDs are in the calibration + for cd in cd_subset: + if cd not in cds_to_calibrate: + raise ValueError(f"CD {cd} not in calibrated CDs list") + + # Get indices of requested CDs + cd_indices = [cds_to_calibrate.index(cd) for cd in cd_subset] + cds_to_process = cd_subset + + print( + f"Processing subset of {len(cd_subset)} CDs: {', '.join(cd_subset[:5])}..." + ) + else: + # Process all CDs + cd_indices = list(range(len(cds_to_calibrate))) + cds_to_process = cds_to_calibrate + print( + f"Processing all {len(cds_to_calibrate)} congressional districts" + ) + + # Generate output path if not provided + if output_path is None: + raise ValueError("No output .h5 path given") + print(f"Output path: {output_path}") + + # Check that output directory exists, create if needed + output_dir_path = os.path.dirname(output_path) + if output_dir_path and not os.path.exists(output_dir_path): + print(f"Creating output directory: {output_dir_path}") + os.makedirs(output_dir_path, exist_ok=True) + + # Load the original simulation + base_sim = Microsimulation(dataset=dataset_path) + + household_ids = base_sim.calculate( + "household_id", map_to="household" + ).values + n_households_orig = len(household_ids) + + # From the base sim, create mapping from household ID to index for proper filtering + hh_id_to_idx = {int(hh_id): idx for idx, hh_id in enumerate(household_ids)} + + # Infer the number of households from weight vector and CD count + if len(w) % len(cds_to_calibrate) != 0: + raise ValueError( + f"Weight vector length ({len(w):,}) is not evenly divisible by " + f"number of CDs ({len(cds_to_calibrate)}). Cannot determine household count." + ) + n_households_from_weights = len(w) // len(cds_to_calibrate) + + if n_households_from_weights != n_households_orig: + raise ValueError( + "Households from base data set do not match households from weights" + ) + + print(f"\nOriginal dataset has {n_households_orig:,} households") + + # Process the weight vector to understand active household-CD pairs + W_full = w.reshape(len(cds_to_calibrate), n_households_orig) + # (436, 10580) + + # Extract only the CDs we want to process + if cd_subset is not None: + W = W_full[cd_indices, :] + print( + f"Extracted weights for {len(cd_indices)} CDs from full weight matrix" + ) + else: + W = W_full + + # Count total active weights: i.e., number of active households + total_active_weights = np.sum(W > 0) + total_weight_in_W = np.sum(W) + print(f"Total active household-CD pairs: {total_active_weights:,}") + print(f"Total weight in W matrix: {total_weight_in_W:,.0f}") + + # Collect DataFrames for each CD + cd_dfs = [] + total_kept_households = 0 + time_period = int(base_sim.default_calculation_period) + + for idx, cd_geoid in enumerate(cds_to_process): + # Progress every 10 CDs and at the end ---- + if (idx + 1) % 10 == 0 or (idx + 1) == len(cds_to_process): + print( + f"Processing CD {cd_geoid} ({idx + 1}/{len(cds_to_process)})..." + ) + + # Get the correct index in the weight matrix + cd_idx = idx # Index in our filtered W matrix + + # Get ALL households with non-zero weight in this CD + active_household_indices = np.where(W[cd_idx, :] > 0)[0] + + if len(active_household_indices) == 0: + continue + + # Get the household IDs for active households + active_household_ids = set( + household_ids[hh_idx] for hh_idx in active_household_indices + ) + + # Fresh simulation + cd_sim = Microsimulation(dataset=dataset_path) + + # First, create hh_df with CALIBRATED weights from the W matrix + household_ids_in_sim = cd_sim.calculate( + "household_id", map_to="household" + ).values + + # Get this CD's calibrated weights from the weight matrix + calibrated_weights_for_cd = W[ + cd_idx, : + ] # Get this CD's row from weight matrix + + # Map the calibrated weights to household IDs + hh_weight_values = [] + for hh_id in household_ids_in_sim: + hh_idx = hh_id_to_idx[int(hh_id)] # Get index in weight matrix + hh_weight_values.append(calibrated_weights_for_cd[hh_idx]) + + entity_rel = pd.DataFrame( + { + "person_id": cd_sim.calculate( + "person_id", map_to="person" + ).values, + "household_id": cd_sim.calculate( + "household_id", map_to="person" + ).values, + "tax_unit_id": cd_sim.calculate( + "tax_unit_id", map_to="person" + ).values, + "spm_unit_id": cd_sim.calculate( + "spm_unit_id", map_to="person" + ).values, + "family_id": cd_sim.calculate( + "family_id", map_to="person" + ).values, + "marital_unit_id": cd_sim.calculate( + "marital_unit_id", map_to="person" + ).values, + } + ) + + hh_df = pd.DataFrame( + { + "household_id": household_ids_in_sim, + "household_weight": hh_weight_values, + } + ) + counts = ( + entity_rel.groupby("household_id")["person_id"] + .size() + .reset_index(name="persons_per_hh") + ) + hh_df = hh_df.merge(counts) + hh_df["per_person_hh_weight"] = ( + hh_df.household_weight / hh_df.persons_per_hh + ) + + # SET WEIGHTS IN SIMULATION BEFORE EXTRACTING DATAFRAME + # This is the key - set_input updates the simulation's internal state + + non_household_cols = [ + "person_id", + "tax_unit_id", + "spm_unit_id", + "family_id", + "marital_unit_id", + ] + + new_weights_per_id = {} + for col in non_household_cols: + person_counts = ( + entity_rel.groupby(col)["person_id"] + .size() + .reset_index(name="person_id_count") + ) + # Below: drop duplicates to undo the broadcast join done in entity_rel + id_link = entity_rel[["household_id", col]].drop_duplicates() + hh_info = id_link.merge(hh_df) + + hh_info2 = hh_info.merge(person_counts, on=col) + if col == "person_id": + # Person weight = household weight (each person represents same count as their household) + hh_info2["id_weight"] = hh_info2.household_weight + else: + hh_info2["id_weight"] = ( + hh_info2.per_person_hh_weight * hh_info2.person_id_count + ) + new_weights_per_id[col] = hh_info2.id_weight + + cd_sim.set_input( + "household_weight", time_period, hh_df.household_weight.values + ) + cd_sim.set_input( + "person_weight", time_period, new_weights_per_id["person_id"] + ) + cd_sim.set_input( + "tax_unit_weight", time_period, new_weights_per_id["tax_unit_id"] + ) + cd_sim.set_input( + "spm_unit_weight", time_period, new_weights_per_id["spm_unit_id"] + ) + cd_sim.set_input( + "marital_unit_weight", + time_period, + new_weights_per_id["marital_unit_id"], + ) + cd_sim.set_input( + "family_weight", time_period, new_weights_per_id["family_id"] + ) + + # Extract state from CD GEOID and update simulation BEFORE calling to_input_dataframe() + # This ensures calculated variables (SNAP, Medicaid) use the correct state + cd_geoid_int = int(cd_geoid) + state_fips = cd_geoid_int // 100 + + cd_sim.set_input( + "state_fips", + time_period, + np.full(n_households_orig, state_fips, dtype=np.int32), + ) + cd_sim.set_input( + "congressional_district_geoid", + time_period, + np.full(n_households_orig, cd_geoid_int, dtype=np.int32), + ) + + # Set county for this CD + county_indices = assign_counties_for_cd( + cd_geoid=cd_geoid, n_households=n_households_orig, seed=42 + idx + ) + cd_sim.set_input("county", time_period, county_indices) + + # Delete cached calculated variables to ensure they're recalculated + # with new state and county. Exclude 'county' itself since we just set it. + for var in get_calculated_variables(cd_sim): + if var != "county": + cd_sim.delete_arrays(var) + + # Now extract the dataframe - calculated vars will use the updated state + df = cd_sim.to_input_dataframe() + + assert df.shape[0] == entity_rel.shape[0] # df is at the person level + + # Column names follow pattern: variable__year + hh_id_col = f"household_id__{time_period}" + cd_geoid_col = f"congressional_district_geoid__{time_period}" + hh_weight_col = f"household_weight__{time_period}" + person_weight_col = f"person_weight__{time_period}" + tax_unit_weight_col = f"tax_unit_weight__{time_period}" + person_id_col = f"person_id__{time_period}" + tax_unit_id_col = f"tax_unit_id__{time_period}" + + state_fips_col = f"state_fips__{time_period}" + state_name_col = f"state_name__{time_period}" + state_code_col = f"state_code__{time_period}" + + # Filter to only active households in this CD + df_filtered = df[df[hh_id_col].isin(active_household_ids)].copy() + + # Update congressional_district_geoid to target CD + df_filtered[cd_geoid_col] = int(cd_geoid) + + # Update state variables for consistency + df_filtered[state_fips_col] = state_fips + if state_fips in STATE_FIPS_TO_NAME: + df_filtered[state_name_col] = STATE_FIPS_TO_NAME[state_fips] + if state_fips in STATE_FIPS_TO_CODE: + df_filtered[state_code_col] = STATE_FIPS_TO_CODE[state_fips] + + cd_dfs.append(df_filtered) + total_kept_households += len(df_filtered[hh_id_col].unique()) + + print(f"\nCombining {len(cd_dfs)} CD DataFrames...") + print(f"Total households across all CDs: {total_kept_households:,}") + + # Combine all CD DataFrames + combined_df = pd.concat(cd_dfs, ignore_index=True) + print(f"Combined DataFrame shape: {combined_df.shape}") + + # REINDEX ALL IDs TO PREVENT OVERFLOW AND HANDLE DUPLICATES + print("\nReindexing all entity IDs using 25k ranges per CD...") + + # Column names + hh_id_col = f"household_id__{time_period}" + person_id_col = f"person_id__{time_period}" + person_hh_id_col = f"person_household_id__{time_period}" + tax_unit_id_col = f"tax_unit_id__{time_period}" + person_tax_unit_col = f"person_tax_unit_id__{time_period}" + spm_unit_id_col = f"spm_unit_id__{time_period}" + person_spm_unit_col = f"person_spm_unit_id__{time_period}" + marital_unit_id_col = f"marital_unit_id__{time_period}" + person_marital_unit_col = f"person_marital_unit_id__{time_period}" + cd_geoid_col = f"congressional_district_geoid__{time_period}" + + # Build CD index mapping from cds_to_calibrate (avoids database dependency) + cds_sorted = sorted(cds_to_calibrate) + cd_to_index = {cd: idx for idx, cd in enumerate(cds_sorted)} + + # Create household mapping for CSV export + household_mapping = [] + + # First, create a unique row identifier to track relationships + combined_df["_row_idx"] = range(len(combined_df)) + + # Group by household ID AND congressional district to create unique household-CD pairs + hh_groups = ( + combined_df.groupby([hh_id_col, cd_geoid_col])["_row_idx"] + .apply(list) + .to_dict() + ) + + # Assign new household IDs using 25k ranges per CD + hh_row_to_new_id = {} + cd_hh_counters = {} # Track how many households assigned per CD + + for (old_hh_id, cd_geoid), row_indices in hh_groups.items(): + # Calculate the ID range for this CD directly (avoiding function call) + cd_str = str(int(cd_geoid)) + cd_idx = cd_to_index[cd_str] + start_id = cd_idx * 25_000 + end_id = start_id + 24_999 + + # Get the next available ID in this CD's range + if cd_str not in cd_hh_counters: + cd_hh_counters[cd_str] = 0 + + new_hh_id = start_id + cd_hh_counters[cd_str] + + # Check we haven't exceeded the range + if new_hh_id > end_id: + raise ValueError( + f"CD {cd_str} exceeded its 25k household allocation" + ) + + # All rows in the same household-CD pair get the SAME new ID + for row_idx in row_indices: + hh_row_to_new_id[row_idx] = new_hh_id + + # Save the mapping + household_mapping.append( + { + "new_household_id": new_hh_id, + "original_household_id": int(old_hh_id), + "congressional_district": cd_str, + "state_fips": int(cd_str) // 100, + } + ) + + cd_hh_counters[cd_str] += 1 + + # Apply new household IDs based on row index + combined_df["_new_hh_id"] = combined_df["_row_idx"].map(hh_row_to_new_id) + + # Update household IDs + combined_df[hh_id_col] = combined_df["_new_hh_id"] + + # Update person household references - since persons are already in their households, + # person_household_id should just match the household_id of their row + combined_df[person_hh_id_col] = combined_df["_new_hh_id"] + + # Report statistics + total_households = sum(cd_hh_counters.values()) + print( + f" Created {total_households:,} unique households across {len(cd_hh_counters)} CDs" + ) + + # Now handle persons with same 25k range approach - VECTORIZED + print(" Reindexing persons using 25k ranges...") + + # OFFSET PERSON IDs by 5 million to avoid collision with household IDs + PERSON_ID_OFFSET = 5_000_000 + + # Group by CD and assign IDs in bulk for each CD + for cd_geoid_val in combined_df[cd_geoid_col].unique(): + cd_str = str(int(cd_geoid_val)) + + # Calculate the ID range for this CD directly + cd_idx = cd_to_index[cd_str] + start_id = cd_idx * 25_000 + PERSON_ID_OFFSET # Add offset for persons + end_id = start_id + 24_999 + + # Get all rows for this CD + cd_mask = combined_df[cd_geoid_col] == cd_geoid_val + n_persons_in_cd = cd_mask.sum() + + # Check we won't exceed the range + if n_persons_in_cd > (end_id - start_id + 1): + raise ValueError( + f"CD {cd_str} has {n_persons_in_cd} persons, exceeds 25k allocation" + ) + + # Create sequential IDs for this CD + new_person_ids = np.arange(start_id, start_id + n_persons_in_cd) + + # Assign all at once using loc + combined_df.loc[cd_mask, person_id_col] = new_person_ids + + # Tax units - preserve structure within households + print(" Reindexing tax units...") + # Group by household first, then handle tax units within each household + new_tax_id = 0 + for hh_id in combined_df[hh_id_col].unique(): + hh_mask = combined_df[hh_id_col] == hh_id + hh_df = combined_df[hh_mask] + + # Get unique tax units within this household + unique_tax_in_hh = hh_df[person_tax_unit_col].unique() + + # Create mapping for this household's tax units + for old_tax in unique_tax_in_hh: + # Update all persons with this tax unit ID in this household + mask = (combined_df[hh_id_col] == hh_id) & ( + combined_df[person_tax_unit_col] == old_tax + ) + combined_df.loc[mask, person_tax_unit_col] = new_tax_id + # Also update tax_unit_id if it exists in the DataFrame + if tax_unit_id_col in combined_df.columns: + combined_df.loc[mask, tax_unit_id_col] = new_tax_id + new_tax_id += 1 + + # SPM units - preserve structure within households + print(" Reindexing SPM units...") + new_spm_id = 0 + for hh_id in combined_df[hh_id_col].unique(): + hh_mask = combined_df[hh_id_col] == hh_id + hh_df = combined_df[hh_mask] + + # Get unique SPM units within this household + unique_spm_in_hh = hh_df[person_spm_unit_col].unique() + + for old_spm in unique_spm_in_hh: + # Update all persons with this SPM unit ID in this household + mask = (combined_df[hh_id_col] == hh_id) & ( + combined_df[person_spm_unit_col] == old_spm + ) + combined_df.loc[mask, person_spm_unit_col] = new_spm_id + # Also update spm_unit_id if it exists + if spm_unit_id_col in combined_df.columns: + combined_df.loc[mask, spm_unit_id_col] = new_spm_id + new_spm_id += 1 + + # Marital units - preserve structure within households + print(" Reindexing marital units...") + new_marital_id = 0 + for hh_id in combined_df[hh_id_col].unique(): + hh_mask = combined_df[hh_id_col] == hh_id + hh_df = combined_df[hh_mask] + + # Get unique marital units within this household + unique_marital_in_hh = hh_df[person_marital_unit_col].unique() + + for old_marital in unique_marital_in_hh: + # Update all persons with this marital unit ID in this household + mask = (combined_df[hh_id_col] == hh_id) & ( + combined_df[person_marital_unit_col] == old_marital + ) + combined_df.loc[mask, person_marital_unit_col] = new_marital_id + # Also update marital_unit_id if it exists + if marital_unit_id_col in combined_df.columns: + combined_df.loc[mask, marital_unit_id_col] = new_marital_id + new_marital_id += 1 + + # Clean up temporary columns + temp_cols = [col for col in combined_df.columns if col.startswith("_")] + combined_df = combined_df.drop(columns=temp_cols) + + print(f" Final persons: {len(combined_df):,}") + print(f" Final households: {total_households:,}") + print(f" Final tax units: {new_tax_id:,}") + print(f" Final SPM units: {new_spm_id:,}") + print(f" Final marital units: {new_marital_id:,}") + + # Check weights in combined_df AFTER reindexing + print(f"\nWeights in combined_df AFTER reindexing:") + print(f" HH weight sum: {combined_df[hh_weight_col].sum()/1e6:.2f}M") + print( + f" Person weight sum: {combined_df[person_weight_col].sum()/1e6:.2f}M" + ) + print( + f" Ratio: {combined_df[person_weight_col].sum() / combined_df[hh_weight_col].sum():.2f}" + ) + + # Verify no overflow risk + max_person_id = combined_df[person_id_col].max() + print(f"\nOverflow check:") + print(f" Max person ID after reindexing: {max_person_id:,}") + print(f" Max person ID × 100: {max_person_id * 100:,}") + print(f" int32 max: {2_147_483_647:,}") + if max_person_id * 100 < 2_147_483_647: + print(" ✓ No overflow risk!") + else: + print(" ⚠️ WARNING: Still at risk of overflow!") + + # Create Dataset from combined DataFrame + print("\nCreating Dataset from combined DataFrame...") + sparse_dataset = Dataset.from_dataframe(combined_df, time_period) + + # Build a simulation to convert to h5 + print("Building simulation from Dataset...") + sparse_sim = Microsimulation() + sparse_sim.dataset = sparse_dataset + sparse_sim.build_from_dataset() + + # Save to h5 file + print(f"\nSaving to {output_path}...") + data = {} + + # Only save input variables (not calculated/derived variables) + # Calculated variables like state_name, state_code will be recalculated on load + input_vars = set(sparse_sim.input_variables) + print( + f"Found {len(input_vars)} input variables (excluding calculated variables)" + ) + + vars_to_save = input_vars.copy() + + # congressional_district_geoid isn't in the original microdata and has no formula, + # so it's not in input_vars. Since we set it explicitly during stacking, save it. + vars_to_save.add("congressional_district_geoid") + + # county is set explicitly with assign_counties_for_cd, must be saved + vars_to_save.add("county") + + variables_saved = 0 + variables_skipped = 0 + + for variable in sparse_sim.tax_benefit_system.variables: + if variable not in vars_to_save: + variables_skipped += 1 + continue + + # Only process variables that have actual data + data[variable] = {} + for period in sparse_sim.get_holder(variable).get_known_periods(): + values = sparse_sim.get_holder(variable).get_array(period) + + # Handle different value types + if ( + sparse_sim.tax_benefit_system.variables.get( + variable + ).value_type + in (Enum, str) + and variable != "county_fips" + ): + # Handle EnumArray objects + if hasattr(values, "decode_to_str"): + values = values.decode_to_str().astype("S") + else: + # Already a regular numpy array, just convert to string type + values = values.astype("S") + elif variable == "county_fips": + values = values.astype("int32") + else: + values = np.array(values) + + if values is not None: + data[variable][period] = values + variables_saved += 1 + + if len(data[variable]) == 0: + del data[variable] + + print(f"Variables saved: {variables_saved}") + print(f"Variables skipped: {variables_skipped}") + + # Write to h5 + with h5py.File(output_path, "w") as f: + for variable, periods in data.items(): + grp = f.create_group(variable) + for period, values in periods.items(): + grp.create_dataset(str(period), data=values) + + print(f"Sparse CD-stacked dataset saved successfully!") + + # Save household mapping to CSV in a mappings subdirectory + mapping_df = pd.DataFrame(household_mapping) + output_dir = os.path.dirname(output_path) + mappings_dir = ( + os.path.join(output_dir, "mappings") if output_dir else "mappings" + ) + os.makedirs(mappings_dir, exist_ok=True) + csv_filename = os.path.basename(output_path).replace( + ".h5", "_household_mapping.csv" + ) + csv_path = os.path.join(mappings_dir, csv_filename) + mapping_df.to_csv(csv_path, index=False) + print(f"Household mapping saved to {csv_path}") + + # Verify the saved file + print("\nVerifying saved file...") + with h5py.File(output_path, "r") as f: + if "household_id" in f and str(time_period) in f["household_id"]: + hh_ids = f["household_id"][str(time_period)][:] + print(f" Final households: {len(hh_ids):,}") + if "person_id" in f and str(time_period) in f["person_id"]: + person_ids = f["person_id"][str(time_period)][:] + print(f" Final persons: {len(person_ids):,}") + if ( + "household_weight" in f + and str(time_period) in f["household_weight"] + ): + weights = f["household_weight"][str(time_period)][:] + print( + f" Total population (from household weights): {np.sum(weights):,.0f}" + ) + if "person_weight" in f and str(time_period) in f["person_weight"]: + person_weights = f["person_weight"][str(time_period)][:] + print( + f" Total population (from person weights): {np.sum(person_weights):,.0f}" + ) + print( + f" Average persons per household: {np.sum(person_weights) / np.sum(weights):.2f}" + ) + + return output_path + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Create sparse CD-stacked datasets" + ) + parser.add_argument( + "--weights-path", required=True, help="Path to w_cd.npy file" + ) + parser.add_argument( + "--dataset-path", + required=True, + help="Path to stratified dataset .h5 file", + ) + parser.add_argument( + "--db-path", required=True, help="Path to policy_data.db" + ) + parser.add_argument( + "--output-dir", + default="./temp", + help="Output directory for files", + ) + parser.add_argument( + "--mode", + choices=["national", "states", "cds", "single-cd", "single-state"], + default="national", + help="Output mode: national (one file), states (per-state files), cds (per-CD files), single-cd (one CD), single-state (one state)", + ) + parser.add_argument( + "--cd", + type=str, + help="Single CD GEOID to process (only used with --mode single-cd)", + ) + parser.add_argument( + "--state", + type=str, + help="State code to process, e.g. RI, CA, NC (only used with --mode single-state)", + ) + + args = parser.parse_args() + dataset_path_str = args.dataset_path + weights_path_str = args.weights_path + db_path = Path(args.db_path).resolve() + output_dir = args.output_dir + mode = args.mode + + os.makedirs(output_dir, exist_ok=True) + + # Load weights + w = np.load(weights_path_str) + db_uri = f"sqlite:///{db_path}" + + # Get list of CDs from database + cds_to_calibrate = get_all_cds_from_database(db_uri) + print(f"Found {len(cds_to_calibrate)} congressional districts") + + # Verify dimensions + assert_sim = Microsimulation(dataset=dataset_path_str) + n_hh = assert_sim.calculate("household_id", map_to="household").shape[0] + expected_length = len(cds_to_calibrate) * n_hh + + if len(w) != expected_length: + raise ValueError( + f"Weight vector length ({len(w):,}) doesn't match expected ({expected_length:,})" + ) + + if mode == "national": + output_path = f"{output_dir}/national.h5" + print(f"\nCreating national dataset with all CDs: {output_path}") + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + dataset_path=dataset_path_str, + output_path=output_path, + ) + + elif mode == "states": + for state_fips, state_code in STATE_CODES.items(): + cd_subset = [ + cd for cd in cds_to_calibrate if int(cd) // 100 == state_fips + ] + if not cd_subset: + continue + output_path = f"{output_dir}/{state_code}.h5" + print(f"\nCreating {state_code} dataset: {output_path}") + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=cd_subset, + dataset_path=dataset_path_str, + output_path=output_path, + ) + + elif mode == "cds": + for i, cd_geoid in enumerate(cds_to_calibrate): + # Convert GEOID to friendly name: 3705 -> NC-05 + cd_int = int(cd_geoid) + state_fips = cd_int // 100 + district_num = cd_int % 100 + state_code = STATE_CODES.get(state_fips, str(state_fips)) + friendly_name = f"{state_code}-{district_num:02d}" + + output_path = f"{output_dir}/{friendly_name}.h5" + print( + f"\n[{i+1}/{len(cds_to_calibrate)}] Creating {friendly_name}.h5 (GEOID {cd_geoid})" + ) + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=[cd_geoid], + dataset_path=dataset_path_str, + output_path=output_path, + ) + + elif mode == "single-cd": + if not args.cd: + raise ValueError("--cd required with --mode single-cd") + if args.cd not in cds_to_calibrate: + raise ValueError(f"CD {args.cd} not in calibrated CDs list") + output_path = f"{output_dir}/{args.cd}.h5" + print(f"\nCreating single CD dataset: {output_path}") + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=[args.cd], + dataset_path=dataset_path_str, + output_path=output_path, + ) + + elif mode == "single-state": + if not args.state: + raise ValueError("--state required with --mode single-state") + # Find FIPS code for this state + state_code_upper = args.state.upper() + state_fips = None + for fips, code in STATE_CODES.items(): + if code == state_code_upper: + state_fips = fips + break + if state_fips is None: + raise ValueError(f"Unknown state code: {args.state}") + + cd_subset = [ + cd for cd in cds_to_calibrate if int(cd) // 100 == state_fips + ] + if not cd_subset: + raise ValueError(f"No CDs found for state {state_code_upper}") + + output_path = f"{output_dir}/{state_code_upper}.h5" + print( + f"\nCreating {state_code_upper} dataset with {len(cd_subset)} CDs: {output_path}" + ) + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=cd_subset, + dataset_path=dataset_path_str, + output_path=output_path, + ) + + print("\nDone!") diff --git a/policyengine_us_data/storage/calibration_targets/make_county_cd_distributions.py b/policyengine_us_data/storage/calibration_targets/make_county_cd_distributions.py new file mode 100644 index 00000000..4ada2e39 --- /dev/null +++ b/policyengine_us_data/storage/calibration_targets/make_county_cd_distributions.py @@ -0,0 +1,193 @@ +""" +Generate P(county|CD) distributions from Census block-level data. + +Uses 119th Congress block assignments and 2020 Census block populations. +""" + +import re +import requests +import pandas as pd +import us +from io import StringIO +from pathlib import Path + +from policyengine_us.variables.household.demographic.geographic.county.county_enum import ( + County, +) +from policyengine_us_data.storage import STORAGE_FOLDER +from policyengine_us_data.storage.calibration_targets.make_district_mapping import ( + fetch_block_to_district_map, + fetch_block_population, +) + + +def build_county_fips_to_enum_mapping() -> dict: + """ + Build mapping from 5-digit county FIPS to County enum name. + + Downloads Census county FIPS file and matches to County enum names. + """ + # Download Census county reference file + url = "https://www2.census.gov/geo/docs/reference/codes2020/national_county2020.txt" + response = requests.get(url, timeout=60) + df = pd.read_csv( + StringIO(response.text), + delimiter="|", + dtype=str, + usecols=["STATE", "STATEFP", "COUNTYFP", "COUNTYNAME"], + ) + + # Build set of valid enum names for lookup + valid_enum_names = set(County._member_names_) + + fips_to_enum = {} + missing = [] + + for _, row in df.iterrows(): + county_fips = row["STATEFP"] + row["COUNTYFP"] + state_code = row["STATE"] + county_name = row["COUNTYNAME"] + + # Transform to enum name format: + # 1. Uppercase + # 2. Replace special chars (periods, apostrophes) with nothing + # 3. Replace hyphens and spaces with underscores + # 4. Append state code + enum_name = county_name.upper() + enum_name = re.sub(r"[.'\"]", "", enum_name) + enum_name = enum_name.replace("-", "_") + enum_name = enum_name.replace(" ", "_") + enum_name = f"{enum_name}_{state_code}" + + if enum_name in valid_enum_names: + fips_to_enum[county_fips] = enum_name + else: + missing.append((county_fips, county_name, state_code, enum_name)) + + if missing: + print(f"Warning: {len(missing)} counties not found in enum:") + for fips, name, state, attempted in missing[:10]: + print(f" {fips}: {name} ({state}) -> tried '{attempted}'") + if len(missing) > 10: + print(f" ... and {len(missing) - 10} more") + + print(f"Mapped {len(fips_to_enum)} counties to enum names") + return fips_to_enum + + +def build_county_cd_distributions(): + """ + Build P(county|CD) distributions from Census block data. + + Algorithm: + 1. Get block → CD mapping (119th Congress) + 2. Get block population (2020 Census) + 3. Extract county FIPS from block GEOID (positions 0-4) + 4. Group by (CD, county_fips) and sum population + 5. Calculate P(county|CD) = pop(county,CD) / pop(CD) + 6. Map county FIPS to County enum names + 7. Save as CSV + """ + print("Building P(county|CD) distributions from Census block data...") + + # Step 1: Block to CD mapping (119th Congress) + print("\nFetching 119th Congress block-to-CD mapping...") + bef = fetch_block_to_district_map(119) + # Filter out 'ZZ' (unassigned blocks) + bef = bef[bef["CD119"] != "ZZ"] + print(f" {len(bef):,} blocks with CD assignments") + + # Step 2: Block population (all 50 states + DC) + print("\nFetching block population data (this takes a few minutes)...") + state_pops = [] + + # Get 50 states + states_to_process = [ + s + for s in us.states.STATES_AND_TERRITORIES + if not s.is_territory and s.abbr not in ["ZZ"] + ] + # Note: DC excluded - handled as special case below (1 county, 1 CD) + + for i, s in enumerate(states_to_process): + print(f" {s.abbr} ({i + 1}/{len(states_to_process)})") + state_pops.append(fetch_block_population(s.abbr)) + + block_pop = pd.concat(state_pops, ignore_index=True) + print(f" Total blocks with population: {len(block_pop):,}") + + # Step 3: Merge and extract county FIPS + print("\nMerging block data...") + df = bef.merge(block_pop, on="GEOID", how="inner") + print(f" Matched blocks: {len(df):,}") + + df["county_fips"] = df["GEOID"].str[:5] + df["state_fips"] = df["GEOID"].str[:2] + + # Create CD geoid in our format: state_fips * 100 + district + # Examples: AL-1 = 101, NY-10 = 3610, DC = 1198 + df["cd_geoid"] = df["state_fips"].astype(int) * 100 + df["CD119"].astype( + int + ) + + # Step 4: Aggregate by (CD, county) + print("\nAggregating population by CD and county...") + cd_county_pop = ( + df.groupby(["cd_geoid", "county_fips"])["POP20"].sum().reset_index() + ) + print(f" Unique CD-county pairs: {len(cd_county_pop):,}") + + # Step 5: Calculate P(county|CD) + cd_totals = cd_county_pop.groupby("cd_geoid")["POP20"].transform("sum") + cd_county_pop["probability"] = cd_county_pop["POP20"] / cd_totals + + # Step 6: Map county FIPS to enum names + print("\nMapping county FIPS to enum names...") + fips_to_enum = build_county_fips_to_enum_mapping() + cd_county_pop["county_name"] = cd_county_pop["county_fips"].map( + fips_to_enum + ) + + # Check for unmapped counties + unmapped = cd_county_pop[cd_county_pop["county_name"].isna()] + if len(unmapped) > 0: + print(f"Warning: {len(unmapped)} rows have unmapped county FIPS") + # Drop unmapped and renormalize + cd_county_pop = cd_county_pop.dropna(subset=["county_name"]) + cd_totals = cd_county_pop.groupby("cd_geoid")["POP20"].transform("sum") + cd_county_pop["probability"] = cd_county_pop["POP20"] / cd_totals + + # Step 7: Add DC (special case: 1 county, 1 CD, probability = 1.0) + # DC: cd_geoid = 1198 (state 11, district 98 at-large) + dc_row = pd.DataFrame( + { + "cd_geoid": [1198], + "county_name": ["DISTRICT_OF_COLUMBIA_DC"], + "probability": [1.0], + } + ) + cd_county_pop = pd.concat([cd_county_pop, dc_row], ignore_index=True) + + # Step 8: Save CSV + output = cd_county_pop[["cd_geoid", "county_name", "probability"]] + output = output.sort_values( + ["cd_geoid", "probability"], ascending=[True, False] + ) + + output_path = STORAGE_FOLDER / "county_cd_distributions.csv" + output.to_csv(output_path, index=False) + print(f"\nSaved to {output_path}") + print(f" Total rows: {len(output):,}") + print(f" Unique CDs: {output['cd_geoid'].nunique()}") + + # Verify probabilities sum to 1 for each CD + cd_sums = output.groupby("cd_geoid")["probability"].sum() + bad_sums = cd_sums[~cd_sums.between(0.9999, 1.0001)] + if len(bad_sums) > 0: + print(f"Warning: {len(bad_sums)} CDs don't sum to 1.0") + else: + print(" All CD probabilities sum to 1.0 ✓") + + +if __name__ == "__main__": + build_county_cd_distributions() diff --git a/policyengine_us_data/storage/county_cd_distributions.csv b/policyengine_us_data/storage/county_cd_distributions.csv new file mode 100644 index 00000000..6c3f06d8 --- /dev/null +++ b/policyengine_us_data/storage/county_cd_distributions.csv @@ -0,0 +1,3720 @@ +cd_geoid,county_name,probability +101,BALDWIN_COUNTY_AL,0.3229058981210833 +101,MOBILE_COUNTY_AL,0.2199388648478448 +101,HOUSTON_COUNTY_AL,0.14935757933776753 +101,COFFEE_COUNTY_AL,0.07448930970778289 +101,DALE_COUNTY_AL,0.06872271000927895 +101,COVINGTON_COUNTY_AL,0.05234383925411771 +101,ESCAMBIA_COUNTY_AL,0.05121113919253672 +101,GENEVA_COUNTY_AL,0.03714225208079648 +101,HENRY_COUNTY_AL,0.023888407448791647 +102,MOBILE_COUNTY_AL,0.3579875556249077 +102,MONTGOMERY_COUNTY_AL,0.3189867280433129 +102,RUSSELL_COUNTY_AL,0.08245582748406836 +102,PIKE_COUNTY_AL,0.0459892943821978 +102,BARBOUR_COUNTY_AL,0.0351415666091725 +102,MONROE_COUNTY_AL,0.027547042580048316 +102,MACON_COUNTY_AL,0.02721266617810559 +102,BUTLER_COUNTY_AL,0.02654252013921204 +102,WASHINGTON_COUNTY_AL,0.021439100304561174 +102,CRENSHAW_COUNTY_AL,0.01838234269680141 +102,CONECUH_COUNTY_AL,0.016157346388874182 +102,BULLOCK_COUNTY_AL,0.01442973497883676 +102,CLARKE_COUNTY_AL,0.007728274589901276 +103,LEE_COUNTY_AL,0.24275866104542781 +103,CALHOUN_COUNTY_AL,0.16222967757755444 +103,ETOWAH_COUNTY_AL,0.14411065629728292 +103,ST_CLAIR_COUNTY_AL,0.12692788894245102 +103,TALLADEGA_COUNTY_AL,0.11176531234935647 +103,TALLAPOOSA_COUNTY_AL,0.057555931419399964 +103,CHAMBERS_COUNTY_AL,0.048445567701468746 +103,CHEROKEE_COUNTY_AL,0.03479047138713264 +103,RANDOLPH_COUNTY_AL,0.030605193422816174 +103,CLEBURNE_COUNTY_AL,0.020976546281873735 +103,CLAY_COUNTY_AL,0.019834093575236085 +104,MARSHALL_COUNTY_AL,0.1359964556101394 +104,CULLMAN_COUNTY_AL,0.12241798722124851 +104,TUSCALOOSA_COUNTY_AL,0.12131175862482131 +104,LAUDERDALE_COUNTY_AL,0.10673016103010223 +104,DEKALB_COUNTY_AL,0.09976677245964495 +104,WALKER_COUNTY_AL,0.09103676189892358 +104,BLOUNT_COUNTY_AL,0.08238755896867171 +104,COLBERT_COUNTY_AL,0.07973065980823513 +104,FRANKLIN_COUNTY_AL,0.04474095581494495 +104,MARION_COUNTY_AL,0.040878908372506455 +104,WINSTON_COUNTY_AL,0.03279675209054913 +104,FAYETTE_COUNTY_AL,0.022738988567113524 +104,LAMAR_COUNTY_AL,0.019466279533099085 +105,MADISON_COUNTY_AL,0.5407883480969803 +105,MORGAN_COUNTY_AL,0.1719544579340554 +105,LIMESTONE_COUNTY_AL,0.1442973497883676 +105,JACKSON_COUNTY_AL,0.07325490349061099 +105,LAWRENCE_COUNTY_AL,0.04607846142271586 +105,LAUDERDALE_COUNTY_AL,0.023626479267269844 +106,JEFFERSON_COUNTY_AL,0.37384065593412796 +106,SHELBY_COUNTY_AL,0.3107244115331833 +106,ELMORE_COUNTY_AL,0.12257246553489701 +106,AUTAUGA_COUNTY_AL,0.08192907050455936 +106,CHILTON_COUNTY_AL,0.06271499327765045 +106,BIBB_COUNTY_AL,0.031059344762488592 +106,COOSA_COUNTY_AL,0.014471511866862649 +106,TALLADEGA_COUNTY_AL,0.0026875465862306776 +107,JEFFERSON_COUNTY_AL,0.5662037411146438 +107,TUSCALOOSA_COUNTY_AL,0.19500274467296594 +107,DALLAS_COUNTY_AL,0.05358660488133817 +107,MARENGO_COUNTY_AL,0.0269214800614138 +107,PICKENS_COUNTY_AL,0.02664283305979486 +107,CLARKE_COUNTY_AL,0.024437342041980958 +107,HALE_COUNTY_AL,0.020598979594680072 +107,CHOCTAW_COUNTY_AL,0.017645321377519316 +107,SUMTER_COUNTY_AL,0.017199486174929016 +107,WILCOX_COUNTY_AL,0.014768291085803771 +107,LOWNDES_COUNTY_AL,0.014365646168464405 +107,PERRY_COUNTY_AL,0.011857823153893953 +107,GREENE_COUNTY_AL,0.010769706612571995 +200,ANCHORAGE_MUNICIPALITY_AK,0.4001123760669201 +200,MATANUSKA_SUSITNA_BOROUGH_AK,0.14710686579302745 +200,FAIRBANKS_NORTH_STAR_BOROUGH_AK,0.13140993497849332 +200,KENAI_PENINSULA_BOROUGH_AK,0.08077751049919427 +200,JUNEAU_CITY_AND_BOROUGH_AK,0.04431161416268153 +200,BETHEL_CENSUS_AREA_AK,0.0256431743903461 +200,KETCHIKAN_GATEWAY_BOROUGH_AK,0.019161630579478593 +200,KODIAK_ISLAND_BOROUGH_AK,0.017998029984352525 +200,NORTH_SLOPE_BOROUGH_AK,0.015154283547621763 +200,NOME_CENSUS_AREA_AK,0.013801099856713645 +200,SITKA_CITY_AND_BOROUGH_AK,0.011619520464670916 +200,KUSILVAK_CENSUS_AREA_AK,0.011495879315247839 +200,NORTHWEST_ARCTIC_BOROUGH_AK,0.010705949749489293 +200,CHUGACH_CENSUS_AREA_AK,0.009756660480029893 +200,SOUTHEAST_FAIRBANKS_CENSUS_AREA_AK,0.009352766058581177 +200,PRINCE_OF_WALES_HYDER_CENSUS_AREA_AK,0.00790341702923289 +200,YUKON_KOYUKUK_CENSUS_AREA_AK,0.007340162904083318 +200,ALEUTIANS_WEST_CENSUS_AREA_AK,0.00718767215312819 +200,DILLINGHAM_CENSUS_AREA_AK,0.006672500697198704 +200,ALEUTIANS_EAST_BOROUGH_AK,0.004698363678076913 +200,COPPER_RIVER_CENSUS_AREA_AK,0.00359520986711324 +200,HOONAH_ANGOON_CENSUS_AREA_AK,0.0032490146487286257 +200,WRANGELL_CITY_AND_BOROUGH_AK,0.002922052498032045 +200,DENALI_BOROUGH_AK,0.0022241668990662347 +200,LAKE_AND_PENINSULA_BOROUGH_AK,0.0020277148505384573 +200,SKAGWAY_MUNICIPALITY_AK,0.0017035002809401673 +200,BRISTOL_BAY_BOROUGH_AK,0.00115947922347863 +200,YAKUTAT_CITY_AND_BOROUGH_AK,0.0009094493435341861 +401,MARICOPA_COUNTY_AZ,1.0 +402,YAVAPAI_COUNTY_AZ,0.2972633184497591 +402,PINAL_COUNTY_AZ,0.2233328970617106 +402,COCONINO_COUNTY_AZ,0.1826061020976275 +402,NAVAJO_COUNTY_AZ,0.13430076565669785 +402,APACHE_COUNTY_AZ,0.08308583308583309 +402,GILA_COUNTY_AZ,0.06704152466864331 +402,GRAHAM_COUNTY_AZ,0.00594000594000594 +402,MARICOPA_COUNTY_AZ,0.004509118915898577 +402,MOHAVE_COUNTY_AZ,0.0019204341238239543 +403,MARICOPA_COUNTY_AZ,1.0 +404,MARICOPA_COUNTY_AZ,1.0 +405,MARICOPA_COUNTY_AZ,0.8013307123476615 +405,PINAL_COUNTY_AZ,0.1986692876523385 +406,PIMA_COUNTY_AZ,0.7193683450141012 +406,COCHISE_COUNTY_AZ,0.12074587439640277 +406,PINAL_COUNTY_AZ,0.1052980640841871 +406,GRAHAM_COUNTY_AZ,0.04255289695209354 +406,GREENLEE_COUNTY_AZ,0.012034819553215347 +407,PIMA_COUNTY_AZ,0.5937685232145037 +407,YUMA_COUNTY_AZ,0.1571624354558394 +407,MARICOPA_COUNTY_AZ,0.14406797791623827 +407,SANTA_CRUZ_COUNTY_AZ,0.05999036006297421 +407,COCHISE_COUNTY_AZ,0.037126342323476516 +407,PINAL_COUNTY_AZ,0.007884361026967913 +408,MARICOPA_COUNTY_AZ,1.0 +409,MARICOPA_COUNTY_AZ,0.6132754098855794 +409,MOHAVE_COUNTY_AZ,0.266470931725169 +409,YUMA_COUNTY_AZ,0.09941707399334518 +409,LA_PAZ_COUNTY_AZ,0.02083658439590643 +501,CRAIGHEAD_COUNTY_AR,0.1478135145227499 +501,LONOKE_COUNTY_AR,0.0983576276164139 +501,CRITTENDEN_COUNTY_AR,0.0640032212239322 +501,GREENE_COUNTY_AR,0.06077801062844431 +501,BAXTER_COUNTY_AR,0.05531761081927259 +501,MISSISSIPPI_COUNTY_AR,0.05406579854858879 +501,INDEPENDENCE_COUNTY_AR,0.05041534386964143 +501,BOONE_COUNTY_AR,0.049664522284783304 +501,ST_FRANCIS_COUNTY_AR,0.030684018397122162 +501,POINSETT_COUNTY_AR,0.030517907427020808 +501,RANDOLPH_COUNTY_AR,0.024678774606018 +501,SHARP_COUNTY_AR,0.022951220516963917 +501,ARKANSAS_COUNTY_AR,0.022789096210144993 +501,CROSS_COUNTY_AR,0.02236916767772877 +501,MARION_COUNTY_AR,0.022359865463403097 +501,JACKSON_COUNTY_AR,0.022265514432385527 +501,PHILLIPS_COUNTY_AR,0.0220170124211139 +501,LAWRENCE_COUNTY_AR,0.021549243929308487 +501,CLAY_COUNTY_AR,0.019337974695319258 +501,IZARD_COUNTY_AR,0.018042309128528694 +501,LINCOLN_COUNTY_AR,0.017197136512653005 +501,STONE_COUNTY_AR,0.0164237238358611 +501,FULTON_COUNTY_AR,0.016046319711790824 +501,DESHA_COUNTY_AR,0.015142676034439456 +501,CHICOT_COUNTY_AR,0.013565286262356995 +501,PULASKI_COUNTY_AR,0.011444381396102904 +501,LEE_COUNTY_AR,0.011428434742973174 +501,PRAIRIE_COUNTY_AR,0.01100584843503533 +501,SEARCY_COUNTY_AR,0.01040253339162721 +501,MONROE_COUNTY_AR,0.009035107885752862 +501,WOODRUFF_COUNTY_AR,0.00833079737252312 +502,PULASKI_COUNTY_AR,0.4752600603153937 +502,FAULKNER_COUNTY_AR,0.16407115622218385 +502,SALINE_COUNTY_AR,0.16396221652429221 +502,WHITE_COUNTY_AR,0.10206055452963293 +502,CLEBURNE_COUNTY_AR,0.032829376519509505 +502,CONWAY_COUNTY_AR,0.02752055904664479 +502,VAN_BUREN_COUNTY_AR,0.02098550570604881 +502,PERRY_COUNTY_AR,0.01331057113629419 +503,BENTON_COUNTY_AR,0.37749047753707754 +503,WASHINGTON_COUNTY_AR,0.32642697542149096 +503,SEBASTIAN_COUNTY_AR,0.15679503570674663 +503,CRAWFORD_COUNTY_AR,0.07983468287443625 +503,CARROLL_COUNTY_AR,0.037518968586825345 +503,MADISON_COUNTY_AR,0.021933859873423268 +504,GARLAND_COUNTY_AR,0.1330259757849701 +504,JEFFERSON_COUNTY_AR,0.08931250879713605 +504,POPE_COUNTY_AR,0.0841617026475064 +504,MILLER_COUNTY_AR,0.056567244644038 +504,UNION_COUNTY_AR,0.051858619068738496 +504,HOT_SPRING_COUNTY_AR,0.04387281133894402 +504,PULASKI_COUNTY_AR,0.04352756524487243 +504,JOHNSON_COUNTY_AR,0.034191314139420996 +504,COLUMBIA_COUNTY_AR,0.03027675458048616 +504,OUACHITA_COUNTY_AR,0.03007624627200612 +504,CLARK_COUNTY_AR,0.028477491282536124 +504,LOGAN_COUNTY_AR,0.028059212360872464 +504,YELL_COUNTY_AR,0.026906621554510374 +504,HEMPSTEAD_COUNTY_AR,0.02664370337517893 +504,POLK_COUNTY_AR,0.025522981439038833 +504,ASHLEY_COUNTY_AR,0.02531185017381813 +504,GRANT_COUNTY_AR,0.02384588214360644 +504,DREW_COUNTY_AR,0.023038537431315946 +504,FRANKLIN_COUNTY_AR,0.022702586424392435 +504,SEVIER_COUNTY_AR,0.021032126476922954 +504,HOWARD_COUNTY_AR,0.016976812741174314 +504,LITTLE_RIVER_COUNTY_AR,0.01596895972040378 +504,BRADLEY_COUNTY_AR,0.01400238485378828 +504,PIKE_COUNTY_AR,0.013505761626162217 +504,SCOTT_COUNTY_AR,0.013060925312646895 +504,SEBASTIAN_COUNTY_AR,0.012877679308870434 +504,MONTGOMERY_COUNTY_AR,0.01126564562347461 +504,NEVADA_COUNTY_AR,0.01103459631436516 +504,CLEVELAND_COUNTY_AR,0.01002541542400204 +504,NEWTON_COUNTY_AR,0.009593857806412549 +504,DALLAS_COUNTY_AR,0.008607250699123341 +504,LAFAYETTE_COUNTY_AR,0.008376201390013889 +504,CALHOUN_COUNTY_AR,0.006292773999251082 +601,BUTTE_COUNTY_CA,0.2767958272406478 +601,SHASTA_COUNTY_CA,0.23824253378988147 +601,SUTTER_COUNTY_CA,0.1303110997177528 +601,YUBA_COUNTY_CA,0.0903334911545977 +601,TEHAMA_COUNTY_CA,0.08609847523732046 +601,SISKIYOU_COUNTY_CA,0.05764748658737238 +601,LASSEN_COUNTY_CA,0.04280792803350345 +601,GLENN_COUNTY_CA,0.037820863273596676 +601,COLUSA_COUNTY_CA,0.02856346899858484 +601,MODOC_COUNTY_CA,0.011378825966742438 +602,MARIN_COUNTY_CA,0.34257278599207563 +602,SONOMA_COUNTY_CA,0.30232011471286524 +602,HUMBOLDT_COUNTY_CA,0.17821108525370297 +602,MENDOCINO_COUNTY_CA,0.11962446685419817 +602,DEL_NORTE_COUNTY_CA,0.0362304077896095 +602,TRINITY_COUNTY_CA,0.02104113939754851 +602,SAN_FRANCISCO_COUNTY_CA,0.0 +603,PLACER_COUNTY_CA,0.5293266024090083 +603,SACRAMENTO_COUNTY_CA,0.16285523717353492 +603,NEVADA_COUNTY_CA,0.13371303767835424 +603,EL_DORADO_COUNTY_CA,0.08393209787740476 +603,PLUMAS_COUNTY_CA,0.02588179904005859 +603,INYO_COUNTY_CA,0.0248695447471326 +603,MONO_COUNTY_CA,0.017256712396845533 +603,YUBA_COUNTY_CA,0.016358238625217427 +603,SIERRA_COUNTY_CA,0.004232112263447681 +603,ALPINE_COUNTY_CA,0.001574617788995985 +604,SONOMA_COUNTY_CA,0.3363645987581309 +604,YOLO_COUNTY_CA,0.21215677715232698 +604,SOLANO_COUNTY_CA,0.1820079279919805 +604,NAPA_COUNTY_CA,0.18038468927786788 +604,LAKE_COUNTY_CA,0.08908600681969372 +605,STANISLAUS_COUNTY_CA,0.42711729258441067 +605,EL_DORADO_COUNTY_CA,0.166021359197919 +605,FRESNO_COUNTY_CA,0.15991032796956903 +605,TUOLUMNE_COUNTY_CA,0.0727049319616737 +605,CALAVERAS_COUNTY_CA,0.059204454843727536 +605,AMADOR_COUNTY_CA,0.05290649795427511 +605,MADERA_COUNTY_CA,0.03974196415733128 +605,MARIPOSA_COUNTY_CA,0.02239317133109371 +606,SACRAMENTO_COUNTY_CA,1.0 +607,SACRAMENTO_COUNTY_CA,0.928350393077613 +607,YOLO_COUNTY_CA,0.0713264390861605 +607,SOLANO_COUNTY_CA,0.0003231678362264549 +608,CONTRA_COSTA_COUNTY_CA,0.5857985433659302 +608,SOLANO_COUNTY_CA,0.4142014566340698 +609,SAN_JOAQUIN_COUNTY_CA,0.9742253903722824 +609,CONTRA_COSTA_COUNTY_CA,0.0231172304466103 +609,STANISLAUS_COUNTY_CA,0.0026573791811072807 +610,CONTRA_COSTA_COUNTY_CA,0.9263165786711494 +610,ALAMEDA_COUNTY_CA,0.0736834213288507 +611,SAN_FRANCISCO_COUNTY_CA,1.0 +612,ALAMEDA_COUNTY_CA,1.0 +612,SAN_FRANCISCO_COUNTY_CA,0.0 +613,MERCED_COUNTY_CA,0.3661141143017842 +613,STANISLAUS_COUNTY_CA,0.29177472945244715 +613,MADERA_COUNTY_CA,0.16385443031382474 +613,FRESNO_COUNTY_CA,0.13028596277432325 +613,SAN_JOAQUIN_COUNTY_CA,0.04797076315762064 +614,ALAMEDA_COUNTY_CA,1.0 +615,SAN_MATEO_COUNTY_CA,0.8487954807142537 +615,SAN_FRANCISCO_COUNTY_CA,0.15120451928574627 +616,SANTA_CLARA_COUNTY_CA,0.8416534554171394 +616,SAN_MATEO_COUNTY_CA,0.15834654458286057 +617,SANTA_CLARA_COUNTY_CA,0.8572081740645139 +617,ALAMEDA_COUNTY_CA,0.1427918259354861 +618,SANTA_CLARA_COUNTY_CA,0.47355590690631294 +618,MONTEREY_COUNTY_CA,0.3542100306129266 +618,SANTA_CRUZ_COUNTY_CA,0.08812514491035535 +618,SAN_BENITO_COUNTY_CA,0.08410891757040515 +619,SANTA_CLARA_COUNTY_CA,0.37639428115668816 +619,SANTA_CRUZ_COUNTY_CA,0.2683901416527694 +619,MONTEREY_COUNTY_CA,0.22230718019366022 +619,SAN_LUIS_OBISPO_COUNTY_CA,0.1329083969968822 +620,KERN_COUNTY_CA,0.5085585432133058 +620,FRESNO_COUNTY_CA,0.26159504411606704 +620,TULARE_COUNTY_CA,0.1487538226500038 +620,KINGS_COUNTY_CA,0.08109259002062331 +621,FRESNO_COUNTY_CA,0.7755881046318528 +621,TULARE_COUNTY_CA,0.22441189536814715 +622,KERN_COUNTY_CA,0.6414789701973929 +622,TULARE_COUNTY_CA,0.24270722674551645 +622,KINGS_COUNTY_CA,0.11581380305709066 +623,SAN_BERNARDINO_COUNTY_CA,0.9528281990723093 +623,KERN_COUNTY_CA,0.025282911602159286 +623,LOS_ANGELES_COUNTY_CA,0.021888889325531406 +624,SANTA_BARBARA_COUNTY_CA,0.586127896287443 +624,SAN_LUIS_OBISPO_COUNTY_CA,0.23747889775332176 +624,VENTURA_COUNTY_CA,0.17639320595923524 +625,RIVERSIDE_COUNTY_CA,0.7573554642820006 +625,IMPERIAL_COUNTY_CA,0.2335733596755745 +625,SAN_BERNARDINO_COUNTY_CA,0.009071176042424873 +626,VENTURA_COUNTY_CA,0.9318402884866314 +626,LOS_ANGELES_COUNTY_CA,0.06815971151336861 +627,LOS_ANGELES_COUNTY_CA,1.0 +628,LOS_ANGELES_COUNTY_CA,0.9040719575770131 +628,SAN_BERNARDINO_COUNTY_CA,0.09592804242298694 +629,LOS_ANGELES_COUNTY_CA,1.0 +630,LOS_ANGELES_COUNTY_CA,1.0 +631,LOS_ANGELES_COUNTY_CA,1.0 +632,LOS_ANGELES_COUNTY_CA,0.9974338078019628 +632,VENTURA_COUNTY_CA,0.0025661921980372456 +633,SAN_BERNARDINO_COUNTY_CA,1.0 +634,LOS_ANGELES_COUNTY_CA,1.0 +635,SAN_BERNARDINO_COUNTY_CA,0.735241145999352 +635,LOS_ANGELES_COUNTY_CA,0.19980481124539082 +635,RIVERSIDE_COUNTY_CA,0.06495404275525718 +636,LOS_ANGELES_COUNTY_CA,1.0 +637,LOS_ANGELES_COUNTY_CA,1.0 +638,LOS_ANGELES_COUNTY_CA,0.9167281447177926 +638,ORANGE_COUNTY_CA,0.08327185528220735 +639,RIVERSIDE_COUNTY_CA,1.0 +640,ORANGE_COUNTY_CA,0.9107153208509652 +640,SAN_BERNARDINO_COUNTY_CA,0.07491133583823863 +640,RIVERSIDE_COUNTY_CA,0.014373343310796212 +641,RIVERSIDE_COUNTY_CA,1.0 +642,LOS_ANGELES_COUNTY_CA,1.0 +643,LOS_ANGELES_COUNTY_CA,1.0 +644,LOS_ANGELES_COUNTY_CA,1.0 +645,ORANGE_COUNTY_CA,0.8904833030343379 +645,LOS_ANGELES_COUNTY_CA,0.10951669696566216 +646,ORANGE_COUNTY_CA,1.0 +647,ORANGE_COUNTY_CA,1.0 +648,SAN_DIEGO_COUNTY_CA,0.6615143082741357 +648,RIVERSIDE_COUNTY_CA,0.3384856917258643 +649,SAN_DIEGO_COUNTY_CA,0.6830152561053425 +649,ORANGE_COUNTY_CA,0.3169847438946575 +650,SAN_DIEGO_COUNTY_CA,1.0 +651,SAN_DIEGO_COUNTY_CA,1.0 +652,SAN_DIEGO_COUNTY_CA,1.0 +801,DENVER_COUNTY_CO,0.9898907323399574 +801,ARAPAHOE_COUNTY_CO,0.010109267660042621 +801,JEFFERSON_COUNTY_CO,0.0 +802,BOULDER_COUNTY_CO,0.45829130410685587 +802,LARIMER_COUNTY_CO,0.3250554231557945 +802,EAGLE_COUNTY_CO,0.06277642298952503 +802,SUMMIT_COUNTY_CO,0.0430291525799479 +802,ROUTT_COUNTY_CO,0.034402538380535384 +802,WELD_COUNTY_CO,0.029122097212215262 +802,GRAND_COUNTY_CO,0.021777143490550353 +802,CLEAR_CREEK_COUNTY_CO,0.013020284875020783 +802,GILPIN_COUNTY_CO,0.008047442221360085 +802,JEFFERSON_COUNTY_CO,0.0025674776921797925 +802,JACKSON_COUNTY_CO,0.0019107132960150752 +802,BROOMFIELD_COUNTY_CO,0.0 +803,PUEBLO_COUNTY_CO,0.23299848973993045 +803,MESA_COUNTY_CO,0.21573580147700663 +803,GARFIELD_COUNTY_CO,0.08546824989954692 +803,LA_PLATA_COUNTY_CO,0.07708977041275823 +803,MONTROSE_COUNTY_CO,0.05913430230141466 +803,DELTA_COUNTY_CO,0.04322392030260624 +803,MONTEZUMA_COUNTY_CO,0.03581533260360523 +803,OTERO_COUNTY_CO,0.02589611073393097 +803,PITKIN_COUNTY_CO,0.024050545217740707 +803,GUNNISON_COUNTY_CO,0.02344089895113131 +803,ALAMOSA_COUNTY_CO,0.02268992559544428 +803,LAS_ANIMAS_COUNTY_CO,0.020166821387499482 +803,ARCHULETA_COUNTY_CO,0.01850969199007939 +803,MOFFAT_COUNTY_CO,0.018416859490391145 +803,RIO_GRANDE_COUNTY_CO,0.015987973341831434 +803,EAGLE_COUNTY_CO,0.014443074279855347 +803,SAN_MIGUEL_COUNTY_CO,0.011184237872888754 +803,CONEJOS_COUNTY_CO,0.010337660898119796 +803,HUERFANO_COUNTY_CO,0.009449517132445651 +803,RIO_BLANCO_COUNTY_CO,0.009046319260665346 +803,SAGUACHE_COUNTY_CO,0.00882324414947418 +803,OURAY_COUNTY_CO,0.00675321796239591 +803,COSTILLA_COUNTY_CO,0.004848073379241545 +803,DOLORES_COUNTY_CO,0.0032228118548487663 +803,MINERAL_COUNTY_CO,0.0011985091377662007 +803,HINSDALE_COUNTY_CO,0.0010918210411095563 +803,SAN_JUAN_COUNTY_CO,0.0009768195862718744 +804,DOUGLAS_COUNTY_CO,0.49198663330534753 +804,LARIMER_COUNTY_CO,0.14869755082475056 +804,WELD_COUNTY_CO,0.08300844839386307 +804,ARAPAHOE_COUNTY_CO,0.052433242725763864 +804,MORGAN_COUNTY_CO,0.040331451910101776 +804,ELBERT_COUNTY_CO,0.036107254978567295 +804,LOGAN_COUNTY_CO,0.02982568433652815 +804,ADAMS_COUNTY_CO,0.016706982878771506 +804,PROWERS_COUNTY_CO,0.016623856668246065 +804,YUMA_COUNTY_CO,0.013837743178801707 +804,EL_PASO_COUNTY_CO,0.011165235510408787 +804,KIT_CARSON_COUNTY_CO,0.009818590899896647 +804,CROWLEY_COUNTY_CO,0.008204556978861004 +804,LINCOLN_COUNTY_CO,0.007862354078864606 +804,BENT_COUNTY_CO,0.00782771815781234 +804,WASHINGTON_COUNTY_CO,0.006673649268350804 +804,PHILLIPS_COUNTY_CO,0.006276028894670779 +804,BACA_COUNTY_CO,0.004857341568369923 +804,SEDGWICK_COUNTY_CO,0.003330590168385994 +804,CHEYENNE_COUNTY_CO,0.002421743599974508 +804,KIOWA_COUNTY_CO,0.0020033416736631227 +805,EL_PASO_COUNTY_CO,1.0 +806,ARAPAHOE_COUNTY_CO,0.8451490339914727 +806,JEFFERSON_COUNTY_CO,0.08264177662984061 +806,ADAMS_COUNTY_CO,0.06670952955668778 +806,DOUGLAS_COUNTY_CO,0.003969898057451977 +806,DENVER_COUNTY_CO,0.0015297617645469398 +807,JEFFERSON_COUNTY_CO,0.7224860294555748 +807,BROOMFIELD_COUNTY_CO,0.10269129565454754 +807,FREMONT_COUNTY_CO,0.0678110065581539 +807,TELLER_COUNTY_CO,0.03423874562316318 +807,CHAFFEE_COUNTY_CO,0.026986394567249137 +807,PARK_COUNTY_CO,0.024095984880081252 +807,LAKE_COUNTY_CO,0.010303493017152627 +807,CUSTER_COUNTY_CO,0.006517970838177241 +807,ADAMS_COUNTY_CO,0.004007221867348762 +807,EL_PASO_COUNTY_CO,0.0008618575385514974 +807,BOULDER_COUNTY_CO,0.0 +807,WELD_COUNTY_CO,0.0 +808,ADAMS_COUNTY_CO,0.6325353627172756 +808,WELD_COUNTY_CO,0.34371674352607307 +808,LARIMER_COUNTY_CO,0.023747893756651296 +901,HARTFORD_COUNTY_CT,0.874733253003027 +901,MIDDLESEX_COUNTY_CT,0.06707534363391567 +901,LITCHFIELD_COUNTY_CT,0.05819140336305739 +902,NEW_LONDON_COUNTY_CT,0.3723786308147113 +902,TOLLAND_COUNTY_CT,0.20769619017509997 +902,WINDHAM_COUNTY_CT,0.16142531489708647 +902,HARTFORD_COUNTY_CT,0.12089219454566631 +902,MIDDLESEX_COUNTY_CT,0.11307731132520231 +902,NEW_HAVEN_COUNTY_CT,0.02453035824223365 +903,NEW_HAVEN_COUNTY_CT,0.8411317976286382 +903,FAIRFIELD_COUNTY_CT,0.1112787355325719 +903,MIDDLESEX_COUNTY_CT,0.04758946683878983 +904,FAIRFIELD_COUNTY_CT,0.9823818721583385 +904,NEW_HAVEN_COUNTY_CT,0.017618127841661477 +905,NEW_HAVEN_COUNTY_CT,0.3158991609689 +905,HARTFORD_COUNTY_CT,0.2516178144702706 +905,FAIRFIELD_COUNTY_CT,0.23389569169801536 +905,LITCHFIELD_COUNTY_CT,0.19858733286281405 +1000,NEW_CASTLE_COUNTY_DE,0.5765141199335723 +1000,SUSSEX_COUNTY_DE,0.23978835251952627 +1000,KENT_COUNTY_DE,0.18369752754690144 +1198,DISTRICT_OF_COLUMBIA_DC,1.0 +1201,ESCAMBIA_COUNTY_FL,0.41848181471904694 +1201,OKALOOSA_COUNTY_FL,0.27517189468306247 +1201,SANTA_ROSA_COUNTY_FL,0.24440310391941977 +1201,WALTON_COUNTY_FL,0.06194318667847082 +1202,LEON_COUNTY_FL,0.37986222425024796 +1202,BAY_COUNTY_FL,0.22778369285289923 +1202,JACKSON_COUNTY_FL,0.06151548124661183 +1202,GADSDEN_COUNTY_FL,0.05697452357644942 +1202,WAKULLA_COUNTY_FL,0.04389375745071963 +1202,WALTON_COUNTY_FL,0.03595455662286911 +1202,WASHINGTON_COUNTY_FL,0.03291381800548867 +1202,TAYLOR_COUNTY_FL,0.02833515985653018 +1202,HOLMES_COUNTY_FL,0.025549224475150834 +1202,MADISON_COUNTY_FL,0.023358696655447524 +1202,JEFFERSON_COUNTY_FL,0.018863239563142453 +1202,GULF_COUNTY_FL,0.018449834312895774 +1202,CALHOUN_COUNTY_FL,0.01774262533134171 +1202,FRANKLIN_COUNTY_FL,0.01618650556862072 +1202,LIBERTY_COUNTY_FL,0.010366331652411985 +1202,LAFAYETTE_COUNTY_FL,0.0022503285791729555 +1203,ALACHUA_COUNTY_FL,0.36201299756506905 +1203,MARION_COUNTY_FL,0.26888891488921907 +1203,COLUMBIA_COUNTY_FL,0.09060855072859425 +1203,SUWANNEE_COUNTY_FL,0.056516917764855616 +1203,LEVY_COUNTY_FL,0.0557902085356484 +1203,BRADFORD_COUNTY_FL,0.036794367288464566 +1203,BAKER_COUNTY_FL,0.03673716656201534 +1203,GILCHRIST_COUNTY_FL,0.023223494938385717 +1203,DIXIE_COUNTY_FL,0.021786976694604023 +1203,UNION_COUNTY_FL,0.0209913665903557 +1203,HAMILTON_COUNTY_FL,0.018205431208976355 +1203,LAFAYETTE_COUNTY_FL,0.00844360723381187 +1204,DUVAL_COUNTY_FL,0.5988188049988235 +1204,CLAY_COUNTY_FL,0.2837221032707115 +1204,NASSAU_COUNTY_FL,0.11745909173046498 +1205,DUVAL_COUNTY_FL,0.6954347320210966 +1205,ST_JOHNS_COUNTY_FL,0.3045652679789033 +1206,VOLUSIA_COUNTY_FL,0.33173561304228566 +1206,MARION_COUNTY_FL,0.21979769143068117 +1206,LAKE_COUNTY_FL,0.15226313374179853 +1206,FLAGLER_COUNTY_FL,0.14999330491497243 +1206,PUTNAM_COUNTY_FL,0.09531851054508392 +1206,ST_JOHNS_COUNTY_FL,0.05089174632517833 +1207,SEMINOLE_COUNTY_FL,0.6121205739312889 +1207,VOLUSIA_COUNTY_FL,0.38787942606871106 +1207,ORANGE_COUNTY_FL,0.0 +1208,BREVARD_COUNTY_FL,0.7886056152913142 +1208,INDIAN_RIVER_COUNTY_FL,0.2077270381333843 +1208,ORANGE_COUNTY_FL,0.0036673465753015062 +1209,OSCEOLA_COUNTY_FL,0.5052592167920532 +1209,ORANGE_COUNTY_FL,0.4247088938029513 +1209,POLK_COUNTY_FL,0.07003188940499544 +1210,ORANGE_COUNTY_FL,1.0 +1211,ORANGE_COUNTY_FL,0.43052776770264983 +1211,LAKE_COUNTY_FL,0.3468860054522692 +1211,SUMTER_COUNTY_FL,0.16867974223272636 +1211,POLK_COUNTY_FL,0.053906484612354574 +1212,PASCO_COUNTY_FL,0.5471288485363764 +1212,HERNANDO_COUNTY_FL,0.2528727114834358 +1212,CITRUS_COUNTY_FL,0.19999843998018774 +1212,MARION_COUNTY_FL,0.0 +1213,PINELLAS_COUNTY_FL,1.0 +1214,HILLSBOROUGH_COUNTY_FL,0.7531450649423248 +1214,PINELLAS_COUNTY_FL,0.24685493505767522 +1215,HILLSBOROUGH_COUNTY_FL,0.6641992353302887 +1215,PASCO_COUNTY_FL,0.1833387284018507 +1215,POLK_COUNTY_FL,0.1524620362678606 +1216,MANATEE_COUNTY_FL,0.519629599295911 +1216,HILLSBOROUGH_COUNTY_FL,0.48037040070408893 +1217,SARASOTA_COUNTY_FL,0.5642149655300622 +1217,CHARLOTTE_COUNTY_FL,0.24290418488314802 +1217,LEE_COUNTY_FL,0.19288084958678975 +1218,POLK_COUNTY_FL,0.6661713603762768 +1218,HIGHLANDS_COUNTY_FL,0.13160717141107692 +1218,OKEECHOBEE_COUNTY_FL,0.05153785453075254 +1218,HENDRY_COUNTY_FL,0.051505354117997296 +1218,DESOTO_COUNTY_FL,0.04416936095088408 +1218,HARDEE_COUNTY_FL,0.032925518154080555 +1218,GLADES_COUNTY_FL,0.015764000202802577 +1218,COLLIER_COUNTY_FL,0.006319380256129253 +1219,LEE_COUNTY_FL,0.7962003117439591 +1219,COLLIER_COUNTY_FL,0.20379968825604086 +1220,BROWARD_COUNTY_FL,0.6959274382784661 +1220,PALM_BEACH_COUNTY_FL,0.30407256172153385 +1221,ST_LUCIE_COUNTY_FL,0.427999235590292 +1221,PALM_BEACH_COUNTY_FL,0.36603784868067823 +1221,MARTIN_COUNTY_FL,0.20596291572902975 +1222,PALM_BEACH_COUNTY_FL,1.0 +1223,BROWARD_COUNTY_FL,0.7302361739994098 +1223,PALM_BEACH_COUNTY_FL,0.26976382600059023 +1224,MIAMI_DADE_COUNTY_FL,0.8984440102389301 +1224,BROWARD_COUNTY_FL,0.10155598976106997 +1225,BROWARD_COUNTY_FL,1.0 +1226,MIAMI_DADE_COUNTY_FL,0.7216352647678625 +1226,COLLIER_COUNTY_FL,0.27836473523213745 +1227,MIAMI_DADE_COUNTY_FL,1.0 +1228,MIAMI_DADE_COUNTY_FL,0.892262431732883 +1228,MONROE_COUNTY_FL,0.10773756826711699 +1301,CHATHAM_COUNTY_GA,0.40989923639538645 +1301,GLYNN_COUNTY_GA,0.1172947213974478 +1301,LIBERTY_COUNTY_GA,0.09058313517925483 +1301,CAMDEN_COUNTY_GA,0.07602453640274348 +1301,EFFINGHAM_COUNTY_GA,0.06553035192997214 +1301,WARE_COUNTY_GA,0.05032072504264998 +1301,WAYNE_COUNTY_GA,0.04184347840571683 +1301,PIERCE_COUNTY_GA,0.02736816680756081 +1301,APPLING_COUNTY_GA,0.025602478626427855 +1301,BRANTLEY_COUNTY_GA,0.025015304019022792 +1301,LONG_COUNTY_GA,0.02244311832748241 +1301,CHARLTON_COUNTY_GA,0.01737648164419995 +1301,BACON_COUNTY_GA,0.015463652781305915 +1301,MCINTOSH_COUNTY_GA,0.015234613040828763 +1302,MUSCOGEE_COUNTY_GA,0.22891978822093298 +1302,BIBB_COUNTY_GA,0.1416360730169891 +1302,DOUGHERTY_COUNTY_GA,0.11212371117852098 +1302,HOUSTON_COUNTY_GA,0.06341478715576426 +1302,THOMAS_COUNTY_GA,0.05985594736628865 +1302,LEE_COUNTY_GA,0.043342564795585625 +1302,SUMTER_COUNTY_GA,0.03870679368531387 +1302,DECATUR_COUNTY_GA,0.038381361769199505 +1302,PEACH_COUNTY_GA,0.03656992146504482 +1302,GRADY_COUNTY_GA,0.034289284141271435 +1302,MITCHELL_COUNTY_GA,0.028432816606699194 +1302,CRAWFORD_COUNTY_GA,0.015853370050069465 +1302,MACON_COUNTY_GA,0.015790636186722117 +1302,DOOLY_COUNTY_GA,0.014648357091605817 +1302,EARLY_COUNTY_GA,0.014185694849419124 +1302,CHATTAHOOCHEE_COUNTY_GA,0.012501029227445542 +1302,TERRELL_COUNTY_GA,0.012004386142612369 +1302,SEMINOLE_COUNTY_GA,0.011954721834129052 +1302,TAYLOR_COUNTY_GA,0.010215164081726541 +1302,MARION_COUNTY_GA,0.009799552237050358 +1302,RANDOLPH_COUNTY_GA,0.008397189000139844 +1302,MILLER_COUNTY_GA,0.007841732918418531 +1302,TALBOT_COUNTY_GA,0.007492775803548907 +1302,CALHOUN_COUNTY_GA,0.007283662925724413 +1302,STEWART_COUNTY_GA,0.006945161454746013 +1302,SCHLEY_COUNTY_GA,0.005942726596674844 +1302,BAKER_COUNTY_GA,0.0037588039788952828 +1302,CLAY_COUNTY_GA,0.0037222092252759964 +1302,WEBSTER_COUNTY_GA,0.003068731482074452 +1302,QUITMAN_COUNTY_GA,0.002921045512110903 +1303,COWETA_COUNTY_GA,0.1910222496392798 +1303,CARROLL_COUNTY_GA,0.1557213358148094 +1303,FAYETTE_COUNTY_GA,0.13420489952113088 +1303,TROUP_COUNTY_GA,0.09073681018799272 +1303,SPALDING_COUNTY_GA,0.08796606093557224 +1303,DOUGLAS_COUNTY_GA,0.05615995064929633 +1303,HARRIS_COUNTY_GA,0.04530959202024215 +1303,MUSCOGEE_COUNTY_GA,0.041518109198887515 +1303,HARALSON_COUNTY_GA,0.03910285230338136 +1303,UPSON_COUNTY_GA,0.036202714288701615 +1303,HENRY_COUNTY_GA,0.0313342987390477 +1303,MERIWETHER_COUNTY_GA,0.02694030865101106 +1303,PIKE_COUNTY_GA,0.02468711444762761 +1303,LAMAR_COUNTY_GA,0.024178708098952343 +1303,HEARD_COUNTY_GA,0.01491499550406725 +1304,DEKALB_COUNTY_GA,0.671990767666444 +1304,GWINNETT_COUNTY_GA,0.32800923233355594 +1305,FULTON_COUNTY_GA,0.47366550042672095 +1305,DEKALB_COUNTY_GA,0.3026046315888527 +1305,CLAYTON_COUNTY_GA,0.22372986798442632 +1306,FULTON_COUNTY_GA,0.46580607891930326 +1306,COBB_COUNTY_GA,0.38026573053679347 +1306,DOUGLAS_COUNTY_GA,0.13235163421927604 +1306,FAYETTE_COUNTY_GA,0.021576556324627256 +1307,FULTON_COUNTY_GA,0.47117381040416395 +1307,FORSYTH_COUNTY_GA,0.34033553692815904 +1307,HALL_COUNTY_GA,0.08776591782365553 +1307,CHEROKEE_COUNTY_GA,0.05536887527274057 +1307,LUMPKIN_COUNTY_GA,0.045355859571280945 +1308,LOWNDES_COUNTY_GA,0.15810183142543138 +1308,HOUSTON_COUNTY_GA,0.1539049819371021 +1308,BIBB_COUNTY_GA,0.06547967623157945 +1308,COLQUITT_COUNTY_GA,0.06136572087140447 +1308,BALDWIN_COUNTY_GA,0.05855935353276056 +1308,COFFEE_COUNTY_GA,0.05761409307138789 +1308,TIFT_COUNTY_GA,0.05527701345826281 +1308,JONES_COUNTY_GA,0.03789999759339628 +1308,MONROE_COUNTY_GA,0.037378566787264256 +1308,WORTH_COUNTY_GA,0.027788250960635984 +1308,CRISP_COUNTY_GA,0.026911177604680578 +1308,DODGE_COUNTY_GA,0.02663976618507852 +1308,BERRIEN_COUNTY_GA,0.02427995753681435 +1308,COOK_COUNTY_GA,0.023035208612432516 +1308,BROOKS_COUNTY_GA,0.021794470694251694 +1308,JEFF_DAVIS_COUNTY_GA,0.019759553548269786 +1308,BLECKLEY_COUNTY_GA,0.016823497009126376 +1308,TELFAIR_COUNTY_GA,0.016681774790023824 +1308,LANIER_COUNTY_GA,0.013205569415810317 +1308,PULASKI_COUNTY_GA,0.013176155370336203 +1308,IRWIN_COUNTY_GA,0.01292346197967222 +1308,TURNER_COUNTY_GA,0.012041040615448792 +1308,WILKINSON_COUNTY_GA,0.011868567348805122 +1308,WILCOX_COUNTY_GA,0.011720160119367544 +1308,ATKINSON_COUNTY_GA,0.011078399127205051 +1308,TWIGGS_COUNTY_GA,0.010725430581515679 +1308,CLINCH_COUNTY_GA,0.009023426950218065 +1308,ECHOLS_COUNTY_GA,0.0049428966417182085 +1309,GWINNETT_COUNTY_GA,0.42000431296437885 +1309,HALL_COUNTY_GA,0.18079815980186503 +1309,JACKSON_COUNTY_GA,0.09920732942552621 +1309,HABERSHAM_COUNTY_GA,0.06016062524913904 +1309,GILMER_COUNTY_GA,0.04097708247564155 +1309,WHITE_COUNTY_GA,0.03659877015167258 +1309,STEPHENS_COUNTY_GA,0.035005587249308946 +1309,FANNIN_COUNTY_GA,0.03309089245688669 +1309,UNION_COUNTY_GA,0.03219301169074738 +1309,BANKS_COUNTY_GA,0.0235710038097852 +1309,RABUN_COUNTY_GA,0.022065387153900946 +1309,TOWNS_COUNTY_GA,0.016327837571147575 +1310,CLARKE_COUNTY_GA,0.16816726939097182 +1310,HENRY_COUNTY_GA,0.15481149127541866 +1310,WALTON_COUNTY_GA,0.12634730773704578 +1310,BARROW_COUNTY_GA,0.10913731789208991 +1310,NEWTON_COUNTY_GA,0.055374397003412464 +1310,OCONEE_COUNTY_GA,0.0546294323761627 +1310,MADISON_COUNTY_GA,0.039365499250461025 +1310,GWINNETT_COUNTY_GA,0.034839512401047136 +1310,HART_COUNTY_GA,0.03375604630281897 +1310,BUTTS_COUNTY_GA,0.033241105841176156 +1310,FRANKLIN_COUNTY_GA,0.030614125313505946 +1310,PUTNAM_COUNTY_GA,0.028814447608728894 +1310,MORGAN_COUNTY_GA,0.02626588441024287 +1310,ELBERT_COUNTY_GA,0.025664684886497452 +1310,GREENE_COUNTY_GA,0.024721063025314422 +1310,OGLETHORPE_COUNTY_GA,0.01937561508592579 +1310,JASPER_COUNTY_GA,0.019065866635648257 +1310,HANCOCK_COUNTY_GA,0.011416256173730979 +1310,WILKES_COUNTY_GA,0.0023551337864983657 +1310,TALIAFERRO_COUNTY_GA,0.002037543603302415 +1311,COBB_COUNTY_GA,0.44401968280107434 +1311,CHEROKEE_COUNTY_GA,0.29503159573147225 +1311,BARTOW_COUNTY_GA,0.14232913146046122 +1311,GORDON_COUNTY_GA,0.07520764309566286 +1311,PICKENS_COUNTY_GA,0.04341194691132937 +1312,RICHMOND_COUNTY_GA,0.28344379401221265 +1312,COLUMBIA_COUNTY_GA,0.21402985525100937 +1312,BULLOCH_COUNTY_GA,0.11125958099481834 +1312,LAURENS_COUNTY_GA,0.06800499919743949 +1312,BURKE_COUNTY_GA,0.03374321081824131 +1312,TATTNALL_COUNTY_GA,0.03133690118353667 +1312,EMANUEL_COUNTY_GA,0.03123538070864035 +1312,MCDUFFIE_COUNTY_GA,0.029676904229150917 +1312,WASHINGTON_COUNTY_GA,0.02742150340848135 +1312,EFFINGHAM_COUNTY_GA,0.024091906211543977 +1312,JEFFERSON_COUNTY_GA,0.021551150542517186 +1312,SCREVEN_COUNTY_GA,0.019298493518466436 +1312,CANDLER_COUNTY_GA,0.015064815335628062 +1312,EVANS_COUNTY_GA,0.01478083238558025 +1312,JENKINS_COUNTY_GA,0.011899845935819853 +1312,MONTGOMERY_COUNTY_GA,0.011812044444017632 +1312,WILKES_COUNTY_GA,0.010650046575947604 +1312,LINCOLN_COUNTY_GA,0.010549897999360696 +1312,WHEELER_COUNTY_GA,0.010249452269599968 +1312,TREUTLEN_COUNTY_GA,0.008788380570078623 +1312,WARREN_COUNTY_GA,0.007154449683571655 +1312,GLASCOCK_COUNTY_GA,0.0039565547243376135 +1313,GWINNETT_COUNTY_GA,0.46798608351979254 +1313,CLAYTON_COUNTY_GA,0.16521376591873863 +1313,HENRY_COUNTY_GA,0.12845428786516383 +1313,ROCKDALE_COUNTY_GA,0.12229198469291734 +1313,NEWTON_COUNTY_GA,0.09163599673783485 +1313,DEKALB_COUNTY_GA,0.02441788126555279 +1314,PAULDING_COUNTY_GA,0.22043270738796764 +1314,COBB_COUNTY_GA,0.17703911461491814 +1314,WHITFIELD_COUNTY_GA,0.1344388448589532 +1314,FLOYD_COUNTY_GA,0.1288450680663307 +1314,CATOOSA_COUNTY_GA,0.08870579870768072 +1314,WALKER_COUNTY_GA,0.08842088203927145 +1314,POLK_COUNTY_GA,0.05600703665753539 +1314,MURRAY_COUNTY_GA,0.052242999937266055 +1314,CHATTOOGA_COUNTY_GA,0.032628186361640284 +1314,DADE_COUNTY_GA,0.02123936136843646 +1501,HONOLULU_COUNTY_HI,1.0 +1502,HONOLULU_COUNTY_HI,0.39802792244497004 +1502,HAWAII_COUNTY_HI,0.2752580685877982 +1502,MAUI_COUNTY_HI,0.22603844824085303 +1502,KAUAI_COUNTY_HI,0.10056305873701425 +1502,KALAWAO_COUNTY_HI,0.00011250198936444608 +1601,ADA_COUNTY_ID,0.25463350127725104 +1601,CANYON_COUNTY_ID,0.2513231972490982 +1601,KOOTENAI_COUNTY_ID,0.18635358701456034 +1601,BONNER_COUNTY_ID,0.05123141352374469 +1601,NEZ_PERCE_COUNTY_ID,0.0457722393380262 +1601,LATAH_COUNTY_ID,0.042974140696621074 +1601,PAYETTE_COUNTY_ID,0.027606891609292777 +1601,GEM_COUNTY_ID,0.020795973695915296 +1601,IDAHO_COUNTY_ID,0.0179880876904322 +1601,SHOSHONE_COUNTY_ID,0.014321088615881846 +1601,BOUNDARY_COUNTY_ID,0.013110717924904819 +1601,OWYHEE_COUNTY_ID,0.01295520758455467 +1601,VALLEY_COUNTY_ID,0.012773597606663238 +1601,WASHINGTON_COUNTY_ID,0.011418591424311595 +1601,BENEWAH_COUNTY_ID,0.010363731073684714 +1601,CLEARWATER_COUNTY_ID,0.00949809309523214 +1601,BOISE_COUNTY_ID,0.008275760070382023 +1601,ADAMS_COUNTY_ID,0.004762096366386712 +1601,LEWIS_COUNTY_ID,0.0038420841430564633 +1602,ADA_COUNTY_ID,0.2836356360101049 +1602,BONNEVILLE_COUNTY_ID,0.13480897784032025 +1602,TWIN_FALLS_COUNTY_ID,0.09792366508510113 +1602,BANNOCK_COUNTY_ID,0.09463076081530918 +1602,MADISON_COUNTY_ID,0.05754208838424756 +1602,BINGHAM_COUNTY_ID,0.052190575203386866 +1602,JEFFERSON_COUNTY_ID,0.03359349597032471 +1602,ELMORE_COUNTY_ID,0.031173842073268208 +1602,CASSIA_COUNTY_ID,0.026811940149181177 +1602,BLAINE_COUNTY_ID,0.026395433433418193 +1602,JEROME_COUNTY_ID,0.02635737146200382 +1602,MINIDOKA_COUNTY_ID,0.023503811090823477 +1602,GOODING_COUNTY_ID,0.016962589432039262 +1602,FRANKLIN_COUNTY_ID,0.015435760635874169 +1602,FREMONT_COUNTY_ID,0.014559247808446061 +1602,TETON_COUNTY_ID,0.012647449358547034 +1602,LEMHI_COUNTY_ID,0.00867160457309149 +1602,POWER_COUNTY_ID,0.008567206022926356 +1602,CARIBOU_COUNTY_ID,0.007641756375108341 +1602,BEAR_LAKE_COUNTY_ID,0.006929453767210808 +1602,LINCOLN_COUNTY_ID,0.005575535069756719 +1602,ONEIDA_COUNTY_ID,0.004963281072434106 +1602,CUSTER_COUNTY_ID,0.00464899793704115 +1602,BUTTE_COUNTY_ID,0.002799186126302671 +1602,CAMAS_COUNTY_ID,0.0011712212346651036 +1602,CLARK_COUNTY_ID,0.0008591130690672533 +1701,COOK_COUNTY_IL,0.6871153027092508 +1701,WILL_COUNTY_IL,0.26382389272858264 +1701,KANKAKEE_COUNTY_IL,0.04906080456216655 +1702,COOK_COUNTY_IL,0.6430685824298739 +1702,KANKAKEE_COUNTY_IL,0.0935758952442492 +1702,VERMILION_COUNTY_IL,0.08642959782506299 +1702,WILL_COUNTY_IL,0.06800658637586128 +1702,LIVINGSTON_COUNTY_IL,0.03707954468558812 +1702,IROQUOIS_COUNTY_IL,0.03592653086136369 +1702,CHAMPAIGN_COUNTY_IL,0.022208452692599086 +1702,FORD_COUNTY_IL,0.013704809885401837 +1703,COOK_COUNTY_IL,0.742609897874023 +1703,DUPAGE_COUNTY_IL,0.257390102125977 +1704,COOK_COUNTY_IL,0.9492700453907974 +1704,DUPAGE_COUNTY_IL,0.05072995460920261 +1705,COOK_COUNTY_IL,0.9438393370104169 +1705,LAKE_COUNTY_IL,0.05616066298958307 +1706,COOK_COUNTY_IL,0.5828332296195851 +1706,DUPAGE_COUNTY_IL,0.41716677038041494 +1707,COOK_COUNTY_IL,1.0 +1708,COOK_COUNTY_IL,0.47388868175624305 +1708,KANE_COUNTY_IL,0.30947607529485444 +1708,DUPAGE_COUNTY_IL,0.21663524294890252 +1709,COOK_COUNTY_IL,0.8248122206197085 +1709,MCHENRY_COUNTY_IL,0.11627394759293437 +1709,LAKE_COUNTY_IL,0.058913831787357186 +1710,LAKE_COUNTY_IL,0.803110616351567 +1710,COOK_COUNTY_IL,0.13845321006213537 +1710,MCHENRY_COUNTY_IL,0.05843617358629758 +1711,DUPAGE_COUNTY_IL,0.29584556779628407 +1711,KANE_COUNTY_IL,0.2890973188779809 +1711,MCHENRY_COUNTY_IL,0.21766751539452578 +1711,WILL_COUNTY_IL,0.1063041594741514 +1711,BOONE_COUNTY_IL,0.0350893021811731 +1711,LAKE_COUNTY_IL,0.029624096264049455 +1711,COOK_COUNTY_IL,0.013846780517383441 +1711,DEKALB_COUNTY_IL,0.012525259494451867 +1712,ST_CLAIR_COUNTY_IL,0.12355956198742962 +1712,WILLIAMSON_COUNTY_IL,0.08910050326598795 +1712,JACKSON_COUNTY_IL,0.0702874042859209 +1712,FRANKLIN_COUNTY_IL,0.05015941842460364 +1712,MARION_COUNTY_IL,0.050059906299382895 +1712,JEFFERSON_COUNTY_IL,0.049242580044236456 +1712,CLINTON_COUNTY_IL,0.04895863878027325 +1712,MONROE_COUNTY_IL,0.046388572292905314 +1712,EFFINGHAM_COUNTY_IL,0.04599848476203997 +1712,RANDOLPH_COUNTY_IL,0.040021123107113525 +1712,SALINE_COUNTY_IL,0.03153605589662415 +1712,PERRY_COUNTY_IL,0.02779041950331508 +1712,CRAWFORD_COUNTY_IL,0.024783826493312122 +1712,UNION_COUNTY_IL,0.022879827830755085 +1712,WAYNE_COUNTY_IL,0.02146675565262042 +1712,RICHLAND_COUNTY_IL,0.020981136481543153 +1712,CLARK_COUNTY_IL,0.020506131937156103 +1712,LAWRENCE_COUNTY_IL,0.020273936978307684 +1712,MASSAC_COUNTY_IL,0.01879983069670429 +1712,WHITE_COUNTY_IL,0.0184123968225115 +1712,WASHINGTON_COUNTY_IL,0.018258484735503407 +1712,JOHNSON_COUNTY_IL,0.017657431499170068 +1712,CLAY_COUNTY_IL,0.017630894932444536 +1712,WABASH_COUNTY_IL,0.015074096728439372 +1712,COLES_COUNTY_IL,0.014853843224617443 +1712,CUMBERLAND_COUNTY_IL,0.013865356114091315 +1712,JASPER_COUNTY_IL,0.012322254759001535 +1712,HAMILTON_COUNTY_IL,0.01060533889185951 +1712,EDWARDS_COUNTY_IL,0.008286042960047871 +1712,ALEXANDER_COUNTY_IL,0.006952580482089808 +1712,PULASKI_COUNTY_IL,0.006890219550284803 +1712,GALLATIN_COUNTY_IL,0.006562492951224463 +1712,POPE_COUNTY_IL,0.0049928550294091505 +1712,HARDIN_COUNTY_IL,0.0048415965990736085 +1713,MADISON_COUNTY_IL,0.2416141132076473 +1713,ST_CLAIR_COUNTY_IL,0.21796605177018802 +1713,CHAMPAIGN_COUNTY_IL,0.19927369416872215 +1713,SANGAMON_COUNTY_IL,0.1970353347654234 +1713,MACON_COUNTY_IL,0.07078894539703348 +1713,MACOUPIN_COUNTY_IL,0.05966348979735351 +1713,PIATT_COUNTY_IL,0.013658370893632152 +1714,WILL_COUNTY_IL,0.48580890752935274 +1714,KENDALL_COUNTY_IL,0.17496752587646963 +1714,DEKALB_COUNTY_IL,0.11601256241068787 +1714,LASALLE_COUNTY_IL,0.11521911906559441 +1714,KANE_COUNTY_IL,0.08676263173746844 +1714,BUREAU_COUNTY_IL,0.016288144656132535 +1714,PUTNAM_COUNTY_IL,0.004941108724294359 +1715,MADISON_COUNTY_IL,0.11113514144653479 +1715,ADAMS_COUNTY_IL,0.08722171434182018 +1715,MACON_COUNTY_IL,0.06719854791906878 +1715,SANGAMON_COUNTY_IL,0.06347812126414897 +1715,CHAMPAIGN_COUNTY_IL,0.051665368586277675 +1715,COLES_COUNTY_IL,0.047325313098316654 +1715,CHRISTIAN_COUNTY_IL,0.045154621940168005 +1715,MORGAN_COUNTY_IL,0.04367255468854695 +1715,MONTGOMERY_COUNTY_IL,0.037533319976594745 +1715,LOGAN_COUNTY_IL,0.03713394464737547 +1715,JERSEY_COUNTY_IL,0.02854273116998396 +1715,FAYETTE_COUNTY_IL,0.028510887289913318 +1715,SHELBY_COUNTY_IL,0.02785012677844753 +1715,DOUGLAS_COUNTY_IL,0.02619159135810168 +1715,HANCOCK_COUNTY_IL,0.023378715285195115 +1715,EDGAR_COUNTY_IL,0.0223782867196425 +1715,BOND_COUNTY_IL,0.022191203924227486 +1715,DE_WITT_COUNTY_IL,0.02058706846566898 +1715,PIKE_COUNTY_IL,0.019556122848382 +1715,MOULTRIE_COUNTY_IL,0.019273508412755064 +1715,MASON_COUNTY_IL,0.017362875608516647 +1715,CASS_COUNTY_IL,0.017304495161720473 +1715,MCDONOUGH_COUNTY_IL,0.0168533735273864 +1715,MENARD_COUNTY_IL,0.016316008051194344 +1715,GREENE_COUNTY_IL,0.01590203761027602 +1715,VERMILION_COUNTY_IL,0.01200514278663141 +1715,MERCER_COUNTY_IL,0.010814977768991227 +1715,FULTON_COUNTY_IL,0.009845066255172973 +1715,SCHUYLER_COUNTY_IL,0.009157769176981651 +1715,HENDERSON_COUNTY_IL,0.00847445258379916 +1715,PIATT_COUNTY_IL,0.008463837957108948 +1715,BROWN_COUNTY_IL,0.008284716131711596 +1715,WARREN_COUNTY_IL,0.006782746455046392 +1715,SCOTT_COUNTY_IL,0.006566473436233293 +1715,CALHOUN_COUNTY_IL,0.005887137328059633 +1716,WINNEBAGO_COUNTY_IL,0.17478442356606344 +1716,TAZEWELL_COUNTY_IL,0.15887575181410604 +1716,MCLEAN_COUNTY_IL,0.11424920755177616 +1716,PEORIA_COUNTY_IL,0.07928728089088562 +1716,GRUNDY_COUNTY_IL,0.06970227298962288 +1716,OGLE_COUNTY_IL,0.06871378587909675 +1716,WOODFORD_COUNTY_IL,0.051039105611555086 +1716,LEE_COUNTY_IL,0.04530455354216727 +1716,BOONE_COUNTY_IL,0.035827018736142933 +1716,HENRY_COUNTY_IL,0.0356983163875241 +1716,LASALLE_COUNTY_IL,0.030278222633833857 +1716,JO_DAVIESS_COUNTY_IL,0.029236662389856664 +1716,BUREAU_COUNTY_IL,0.027820936555049445 +1716,MCHENRY_COUNTY_IL,0.019242991361020704 +1716,MARSHALL_COUNTY_IL,0.015579618324560786 +1716,STEPHENSON_COUNTY_IL,0.015261179523854383 +1716,LIVINGSTON_COUNTY_IL,0.010440812178161202 +1716,STARK_COUNTY_IL,0.007164873015894076 +1716,DEKALB_COUNTY_IL,0.004702279623764557 +1716,FORD_COUNTY_IL,0.004252484817766762 +1716,PUTNAM_COUNTY_IL,0.0025382226072972903 +1717,WINNEBAGO_COUNTY_IL,0.2038263126330147 +1717,ROCK_ISLAND_COUNTY_IL,0.19195516375737054 +1717,PEORIA_COUNTY_IL,0.16197013040086192 +1717,MCLEAN_COUNTY_IL,0.11257755321915518 +1717,WHITESIDE_COUNTY_IL,0.07389249491824072 +1717,KNOX_COUNTY_IL,0.0662977194444297 +1717,STEPHENSON_COUNTY_IL,0.043955227445215184 +1717,FULTON_COUNTY_IL,0.03474835340384993 +1717,HENRY_COUNTY_IL,0.029693130735223092 +1717,CARROLL_COUNTY_IL,0.02083388617920698 +1717,MCDONOUGH_COUNTY_IL,0.019286802286393623 +1717,WARREN_COUNTY_IL,0.015554429224228979 +1717,TAZEWELL_COUNTY_IL,0.015393882782521932 +1717,MERCER_COUNTY_IL,0.010014913570287498 +1801,LAKE_COUNTY_IN,0.661452330203582 +1801,PORTER_COUNTY_IN,0.22974426584362032 +1801,LAPORTE_COUNTY_IN,0.10880340395279774 +1802,ST_JOSEPH_COUNTY_IN,0.36197769869765384 +1802,ELKHART_COUNTY_IN,0.2746174465844416 +1802,KOSCIUSKO_COUNTY_IN,0.0821888010695712 +1802,MARSHALL_COUNTY_IN,0.06113824977087249 +1802,MIAMI_COUNTY_IN,0.047698313011392046 +1802,WABASH_COUNTY_IN,0.0410851160625349 +1802,LAPORTE_COUNTY_IN,0.04030124133393992 +1802,STARKE_COUNTY_IN,0.030998200138736543 +1802,FULTON_COUNTY_IN,0.02716371309919663 +1802,PULASKI_COUNTY_IN,0.016597983677897783 +1802,CASS_COUNTY_IN,0.01623323655376306 +1803,ALLEN_COUNTY_IN,0.5111897785918639 +1803,NOBLE_COUNTY_IN,0.06294474280022336 +1803,DEKALB_COUNTY_IN,0.05738467027523155 +1803,LAGRANGE_COUNTY_IN,0.053645680664555995 +1803,HUNTINGTON_COUNTY_IN,0.048626760236462244 +1803,ADAMS_COUNTY_IN,0.047495380975055276 +1803,STEUBEN_COUNTY_IN,0.04567297170756034 +1803,WHITLEY_COUNTY_IN,0.045349341531964446 +1803,WELLS_COUNTY_IN,0.037376632574968795 +1803,JAY_COUNTY_IN,0.027161060392839286 +1803,KOSCIUSKO_COUNTY_IN,0.024237777987046835 +1803,RANDOLPH_COUNTY_IN,0.022850412562156225 +1803,BLACKFORD_COUNTY_IN,0.016064789700071757 +1804,TIPPECANOE_COUNTY_IN,0.2470346058807847 +1804,HENDRICKS_COUNTY_IN,0.23183061939367092 +1804,MORGAN_COUNTY_IN,0.09520563116505537 +1804,BOONE_COUNTY_IN,0.09392172128810115 +1804,MONTGOMERY_COUNTY_IN,0.05031653418609 +1804,PUTNAM_COUNTY_IN,0.04871164683989723 +1804,CLINTON_COUNTY_IN,0.044021662000114065 +1804,JASPER_COUNTY_IN,0.04366089393551536 +1804,CASS_COUNTY_IN,0.03399575832253461 +1804,WHITE_COUNTY_IN,0.032745007275047186 +1804,CARROLL_COUNTY_IN,0.02693292764610775 +1804,NEWTON_COUNTY_IN,0.018343464461029753 +1804,BENTON_COUNTY_IN,0.011564473364838642 +1804,WARREN_COUNTY_IN,0.011194420827989235 +1804,FOUNTAIN_COUNTY_IN,0.008871976412135071 +1804,HOWARD_COUNTY_IN,0.001648657001088936 +1805,HAMILTON_COUNTY_IN,0.4608639599335232 +1805,MADISON_COUNTY_IN,0.172597012787371 +1805,DELAWARE_COUNTY_IN,0.1484228997529004 +1805,HOWARD_COUNTY_IN,0.109311397220229 +1805,GRANT_COUNTY_IN,0.08843327183475762 +1805,TIPTON_COUNTY_IN,0.0203714584712188 +1806,MARION_COUNTY_IN,0.2961128670506891 +1806,JOHNSON_COUNTY_IN,0.21455695279123654 +1806,HANCOCK_COUNTY_IN,0.10589575687480188 +1806,BARTHOLOMEW_COUNTY_IN,0.09822547678954412 +1806,WAYNE_COUNTY_IN,0.08827254893898659 +1806,HENRY_COUNTY_IN,0.06487706728173921 +1806,SHELBY_COUNTY_IN,0.059758683942813105 +1806,FAYETTE_COUNTY_IN,0.031033929350658997 +1806,RUSH_COUNTY_IN,0.022219009508600715 +1806,RANDOLPH_COUNTY_IN,0.009647867428698758 +1806,UNION_COUNTY_IN,0.009399840042230974 +1807,MARION_COUNTY_IN,1.0 +1808,VANDERBURGH_COUNTY_IN,0.23892395619320722 +1808,VIGO_COUNTY_IN,0.14079636897553807 +1808,WARRICK_COUNTY_IN,0.08475131541076494 +1808,DUBOIS_COUNTY_IN,0.057878073657697426 +1808,KNOX_COUNTY_IN,0.048122746028567 +1808,DAVIESS_COUNTY_IN,0.04427499545724036 +1808,GIBSON_COUNTY_IN,0.04378424478113183 +1808,GREENE_COUNTY_IN,0.04085565696262469 +1808,CLAY_COUNTY_IN,0.03510326322672549 +1808,POSEY_COUNTY_IN,0.03345327987245788 +1808,OWEN_COUNTY_IN,0.028279176122459535 +1808,SULLIVAN_COUNTY_IN,0.027610694120408993 +1808,ORANGE_COUNTY_IN,0.02635065860067087 +1808,SPENCER_COUNTY_IN,0.026275056469486583 +1808,PERRY_COUNTY_IN,0.02542619043513669 +1808,PARKE_COUNTY_IN,0.021428561954620153 +1808,VERMILLION_COUNTY_IN,0.020477566725512535 +1808,PIKE_COUNTY_IN,0.016247826438728453 +1808,CRAWFORD_COUNTY_IN,0.013961193558698423 +1808,MARTIN_COUNTY_IN,0.013014177389126821 +1808,FOUNTAIN_COUNTY_IN,0.012984997619196045 +1809,MONROE_COUNTY_IN,0.1853151676242924 +1809,CLARK_COUNTY_IN,0.16061187243682587 +1809,FLOYD_COUNTY_IN,0.10675006764392239 +1809,DEARBORN_COUNTY_IN,0.06721816358687867 +1809,JACKSON_COUNTY_IN,0.06157984370274873 +1809,LAWRENCE_COUNTY_IN,0.05970040374137208 +1809,HARRISON_COUNTY_IN,0.05259513918731796 +1809,JEFFERSON_COUNTY_IN,0.04396457050088335 +1809,RIPLEY_COUNTY_IN,0.03845755940728008 +1809,WASHINGTON_COUNTY_IN,0.03737923570325805 +1809,JENNINGS_COUNTY_IN,0.03662454174558458 +1809,DECATUR_COUNTY_IN,0.03511117477597925 +1809,SCOTT_COUNTY_IN,0.03234175301214407 +1809,FRANKLIN_COUNTY_IN,0.030220917092425473 +1809,BROWN_COUNTY_IN,0.020525288216163447 +1809,SWITZERLAND_COUNTY_IN,0.012914683771294571 +1809,BARTHOLOMEW_COUNTY_IN,0.010811090420028967 +1809,OHIO_COUNTY_IN,0.007878527431600058 +1901,SCOTT_COUNTY_IA,0.21899762282091917 +1901,JOHNSON_COUNTY_IA,0.1916462717406568 +1901,WARREN_COUNTY_IA,0.06570217055507634 +1901,CLINTON_COUNTY_IA,0.05825091777167044 +1901,MUSCATINE_COUNTY_IA,0.054207456518686435 +1901,DES_MOINES_COUNTY_IA,0.048784830187165236 +1901,JASPER_COUNTY_IA,0.047409426467933155 +1901,LEE_COUNTY_IA,0.042070803827559226 +1901,MARION_COUNTY_IA,0.04189401994021946 +1901,WASHINGTON_COUNTY_IA,0.028291690906537745 +1901,MAHASKA_COUNTY_IA,0.02782152099340007 +1901,JONES_COUNTY_IA,0.02588567473770788 +1901,HENRY_COUNTY_IA,0.025680053762362334 +1901,JACKSON_COUNTY_IA,0.024430028686633635 +1901,CEDAR_COUNTY_IA,0.023201317980300507 +1901,IOWA_COUNTY_IA,0.02089058958053321 +1901,JEFFERSON_COUNTY_IA,0.01963805693193444 +1901,LOUISA_COUNTY_IA,0.013587283596461313 +1901,KEOKUK_COUNTY_IA,0.012579239302694136 +1901,VAN_BUREN_COUNTY_IA,0.009031023691548476 +1902,LINN_COUNTY_IA,0.28874395208559794 +1902,BLACK_HAWK_COUNTY_IA,0.16442553746353072 +1902,DUBUQUE_COUNTY_IA,0.12445758404391234 +1902,CERRO_GORDO_COUNTY_IA,0.054071708611828896 +1902,BENTON_COUNTY_IA,0.03206538706025284 +1902,BREMER_COUNTY_IA,0.03132941903662162 +1902,BUCHANAN_COUNTY_IA,0.02578395639859627 +1902,WINNESHIEK_COUNTY_IA,0.0251633360038817 +1902,FAYETTE_COUNTY_IA,0.024459966223205183 +1902,POWESHIEK_COUNTY_IA,0.02339801577002692 +1902,DELAWARE_COUNTY_IA,0.02192607972276448 +1902,TAMA_COUNTY_IA,0.021483495885725606 +1902,CLAYTON_COUNTY_IA,0.02136814825680896 +1902,HARDIN_COUNTY_IA,0.021161274791904102 +1902,FLOYD_COUNTY_IA,0.01959279779435273 +1902,BUTLER_COUNTY_IA,0.017971662096643762 +1902,ALLAMAKEE_COUNTY_IA,0.01762938054561936 +1902,GRUNDY_COUNTY_IA,0.015457836053405952 +1902,CHICKASAW_COUNTY_IA,0.01506038824507359 +1902,MITCHELL_COUNTY_IA,0.013246170646786754 +1902,HOWARD_COUNTY_IA,0.01187202932838843 +1902,WORTH_COUNTY_IA,0.009331873935071823 +1903,POLK_COUNTY_IA,0.617391238930175 +1903,DALLAS_COUNTY_IA,0.12498009531678853 +1903,WAPELLO_COUNTY_IA,0.044432268281276055 +1903,MADISON_COUNTY_IA,0.020748516395816693 +1903,PAGE_COUNTY_IA,0.01907213457195841 +1903,CASS_COUNTY_IA,0.01645913552863704 +1903,APPANOOSE_COUNTY_IA,0.015443526495484302 +1903,UNION_COUNTY_IA,0.015219089437540672 +1903,GUTHRIE_COUNTY_IA,0.013319524394051289 +1903,MONTGOMERY_COUNTY_IA,0.012952149768478756 +1903,CLARKE_COUNTY_IA,0.012222415870583824 +1903,DAVIS_COUNTY_IA,0.011422467027186975 +1903,GREENE_COUNTY_IA,0.010997415839237867 +1903,LUCAS_COUNTY_IA,0.010825639990420676 +1903,DECATUR_COUNTY_IA,0.009585593899324306 +1903,MONROE_COUNTY_IA,0.009500332894071978 +1903,ADAIR_COUNTY_IA,0.009398771990756704 +1903,WAYNE_COUNTY_IA,0.008146187516534993 +1903,TAYLOR_COUNTY_IA,0.007392630690701911 +1903,RINGGOLD_COUNTY_IA,0.005846648051347186 +1903,ADAMS_COUNTY_IA,0.004644217109626845 +1904,WOODBURY_COUNTY_IA,0.13522144632228963 +1904,STORY_COUNTY_IA,0.12577109576329706 +1904,POTTAWATTAMIE_COUNTY_IA,0.1195551034318149 +1904,MARSHALL_COUNTY_IA,0.05118939886120978 +1904,WEBSTER_COUNTY_IA,0.047224948721254226 +1904,SIOUX_COUNTY_IA,0.04578646343222335 +1904,BOONE_COUNTY_IA,0.03409861091078966 +1904,PLYMOUTH_COUNTY_IA,0.03280052791261361 +1904,BUENA_VISTA_COUNTY_IA,0.02657815365881988 +1904,CARROLL_COUNTY_IA,0.02649774143769393 +1904,DICKINSON_COUNTY_IA,0.02259583413639189 +1904,CRAWFORD_COUNTY_IA,0.021092253239782862 +1904,CLAY_COUNTY_IA,0.020912283030596213 +1904,HAMILTON_COUNTY_IA,0.0191955459287803 +1904,KOSSUTH_COUNTY_IA,0.018926228807231483 +1904,HARRISON_COUNTY_IA,0.018612238229501584 +1904,MILLS_COUNTY_IA,0.01848715255219455 +1904,WRIGHT_COUNTY_IA,0.016520244095764573 +1904,LYON_COUNTY_IA,0.01523237217328706 +1904,SHELBY_COUNTY_IA,0.014992411894371527 +1904,CHEROKEE_COUNTY_IA,0.01488009006168766 +1904,HANCOCK_COUNTY_IA,0.013778570270708381 +1904,WINNEBAGO_COUNTY_IA,0.013630509673079648 +1904,FRANKLIN_COUNTY_IA,0.01278809592795065 +1904,CALHOUN_COUNTY_IA,0.012670668557417516 +1904,SAC_COUNTY_IA,0.012526437113175734 +1904,HUMBOLDT_COUNTY_IA,0.012249461684853018 +1904,EMMET_COUNTY_IA,0.011982697332228835 +1904,PALO_ALTO_COUNTY_IA,0.011482354623000703 +1904,MONONA_COUNTY_IA,0.01116964042973312 +1904,POCAHONTAS_COUNTY_IA,0.009034249224277343 +1904,IDA_COUNTY_IA,0.008941073158528226 +1904,FREMONT_COUNTY_IA,0.008430519373601562 +1904,OSCEOLA_COUNTY_IA,0.00790337259066478 +1904,AUDUBON_COUNTY_IA,0.007242205439184747 +2001,DOUGLAS_COUNTY_KS,0.1305989352866693 +2001,RILEY_COUNTY_KS,0.09797404931447166 +2001,RENO_COUNTY_KS,0.0842757362451836 +2001,SALINE_COUNTY_KS,0.07393494628779937 +2001,FINNEY_COUNTY_KS,0.052377905156098956 +2001,FORD_COUNTY_KS,0.04668264190504718 +2001,MCPHERSON_COUNTY_KS,0.04114940024779773 +2001,ELLIS_COUNTY_KS,0.03939439323593884 +2001,BARTON_COUNTY_KS,0.03470938227565455 +2001,POTTAWATOMIE_COUNTY_KS,0.03451196100589541 +2001,SEWARD_COUNTY_KS,0.02990455702751644 +2001,DICKINSON_COUNTY_KS,0.025054801421433143 +2001,JEFFERSON_COUNTY_KS,0.025008509537489617 +2001,JACKSON_COUNTY_KS,0.017076259071166965 +2001,MARSHALL_COUNTY_KS,0.013666997971326263 +2001,RICE_COUNTY_KS,0.012835105586341171 +2001,CLOUD_COUNTY_KS,0.01229730281699729 +2001,CLAY_COUNTY_KS,0.011051506528517161 +2001,THOMAS_COUNTY_KS,0.01079690116682778 +2001,GRANT_COUNTY_KS,0.010009939139787875 +2001,RUSSELL_COUNTY_KS,0.009109970454885836 +2001,ELLSWORTH_COUNTY_KS,0.00868108976540907 +2001,SHERMAN_COUNTY_KS,0.008069764592154888 +2001,MITCHELL_COUNTY_KS,0.007891404686372486 +2001,OTTAWA_COUNTY_KS,0.007808351600473811 +2001,GRAY_COUNTY_KS,0.007696706468610018 +2001,WASHINGTON_COUNTY_KS,0.007529238770814329 +2001,NORTON_COUNTY_KS,0.007432570424932264 +2001,STEVENS_COUNTY_KS,0.007148011491279426 +2001,SCOTT_COUNTY_KS,0.007013220417443871 +2001,PHILLIPS_COUNTY_KS,0.006781760997726252 +2001,ROOKS_COUNTY_KS,0.006697346385829237 +2001,REPUBLIC_COUNTY_KS,0.006363772516236197 +2001,MEADE_COUNTY_KS,0.005520987923264394 +2001,KEARNY_COUNTY_KS,0.005422958051383991 +2001,HASKELL_COUNTY_KS,0.005146568273721187 +2001,SMITH_COUNTY_KS,0.004860647814070009 +2001,OSBORNE_COUNTY_KS,0.004765340994186284 +2001,PAWNEE_COUNTY_KS,0.004476697482538429 +2001,RUSH_COUNTY_KS,0.004024670851089902 +2001,LINCOLN_COUNTY_KS,0.0040015249091181395 +2001,JEWELL_COUNTY_KS,0.003991994227129767 +2001,TREGO_COUNTY_KS,0.0038231650033357387 +2001,DECATUR_COUNTY_KS,0.0037632578594088252 +2001,LOGAN_COUNTY_KS,0.0037605348074121474 +2001,GOVE_COUNTY_KS,0.0037006276634852344 +2001,MORTON_COUNTY_KS,0.0036774817215134725 +2001,NESS_COUNTY_KS,0.003658420357536727 +2001,CHEYENNE_COUNTY_KS,0.0035617520116546625 +2001,RAWLINS_COUNTY_KS,0.003486868081746021 +2001,HAMILTON_COUNTY_KS,0.0034283224638174464 +2001,SHERIDAN_COUNTY_KS,0.003331654117935382 +2001,GRAHAM_COUNTY_KS,0.003288085285988536 +2001,WICHITA_COUNTY_KS,0.002930003948425395 +2001,STANTON_COUNTY_KS,0.0028374201805383475 +2001,CLARK_COUNTY_KS,0.002710798262692826 +2001,HODGEMAN_COUNTY_KS,0.0023459092951379906 +2001,LANE_COUNTY_KS,0.0021430419213854888 +2001,WALLACE_COUNTY_KS,0.0020586273094884747 +2001,GREELEY_COUNTY_KS,0.0017481993818671967 +2002,SHAWNEE_COUNTY_KS,0.24358925483682112 +2002,WYANDOTTE_COUNTY_KS,0.15339088049886312 +2002,LEAVENWORTH_COUNTY_KS,0.11148311026999061 +2002,CRAWFORD_COUNTY_KS,0.0530613912072651 +2002,GEARY_COUNTY_KS,0.050021103652974255 +2002,LYON_COUNTY_KS,0.043812545100548696 +2002,MONTGOMERY_COUNTY_KS,0.04286900758369981 +2002,DOUGLAS_COUNTY_KS,0.031129930426021486 +2002,LABETTE_COUNTY_KS,0.02748104075047313 +2002,CHEROKEE_COUNTY_KS,0.026361866379838523 +2002,ATCHISON_COUNTY_KS,0.022258227020844963 +2002,NEOSHO_COUNTY_KS,0.021653709477582474 +2002,OSAGE_COUNTY_KS,0.0214658188898117 +2002,BOURBON_COUNTY_KS,0.019551513336147155 +2002,ALLEN_COUNTY_KS,0.017054474655193542 +2002,MARION_COUNTY_KS,0.016097321878361267 +2002,NEMAHA_COUNTY_KS,0.013986956580935912 +2002,LINN_COUNTY_KS,0.013058395850068757 +2002,BROWN_COUNTY_KS,0.012945389192206625 +2002,WILSON_COUNTY_KS,0.011741800209675003 +2002,COFFEY_COUNTY_KS,0.011382357346113524 +2002,DONIPHAN_COUNTY_KS,0.010225060247525427 +2002,WABAUNSEE_COUNTY_KS,0.009363214290576878 +2002,MORRIS_COUNTY_KS,0.007333179027053521 +2002,WOODSON_COUNTY_KS,0.0042411534848257925 +2002,CHASE_COUNTY_KS,0.003501844867727749 +2002,JACKSON_COUNTY_KS,0.0009394529388538674 +2003,JOHNSON_COUNTY_KS,0.8303443299249799 +2003,WYANDOTTE_COUNTY_KS,0.07704058709001048 +2003,MIAMI_COUNTY_KS,0.046551935409206636 +2003,FRANKLIN_COUNTY_KS,0.03539422985281904 +2003,ANDERSON_COUNTY_KS,0.010668917722983921 +2004,SEDGWICK_COUNTY_KS,0.713199994553896 +2004,BUTLER_COUNTY_KS,0.09173962176807766 +2004,COWLEY_COUNTY_KS,0.04703936171661198 +2004,HARVEY_COUNTY_KS,0.04632456056748403 +2004,SUMNER_COUNTY_KS,0.030473674894822115 +2004,PRATT_COUNTY_KS,0.012467493566789658 +2004,KINGMAN_COUNTY_KS,0.01017059920759187 +2004,GREENWOOD_COUNTY_KS,0.008190940406007053 +2004,HARPER_COUNTY_KS,0.007467970100889077 +2004,BARBER_COUNTY_KS,0.005756531920977031 +2004,STAFFORD_COUNTY_KS,0.005544133865236157 +2004,CHAUTAUQUA_COUNTY_KS,0.004600596348387272 +2004,PAWNEE_COUNTY_KS,0.004036924585074952 +2004,EDWARDS_COUNTY_KS,0.003957956077171294 +2004,ELK_COUNTY_KS,0.003380669053875584 +2004,KIOWA_COUNTY_KS,0.003349353955913788 +2004,COMANCHE_COUNTY_KS,0.002299617411194467 +2101,CHRISTIAN_COUNTY_KY,0.09763718233490139 +2101,MCCRACKEN_COUNTY_KY,0.09109698893414846 +2101,FRANKLIN_COUNTY_KY,0.06917465792493474 +2101,HOPKINS_COUNTY_KY,0.06096351422992008 +2101,HENDERSON_COUNTY_KY,0.060117973117161126 +2101,CALLOWAY_COUNTY_KY,0.04979700302650033 +2101,GRAVES_COUNTY_KY,0.049187676573813725 +2101,MARSHALL_COUNTY_KY,0.0424904541092627 +2101,BOYLE_COUNTY_KY,0.04108792956508318 +2101,TAYLOR_COUNTY_KY,0.03492621647194615 +2101,LOGAN_COUNTY_KY,0.03358945623653677 +2101,ALLEN_COUNTY_KY,0.027631746713462222 +2101,SIMPSON_COUNTY_KY,0.026297670735553662 +2101,MARION_COUNTY_KY,0.02628022306179832 +2101,ADAIR_COUNTY_KY,0.025370259769019642 +2101,RUSSELL_COUNTY_KY,0.024146238348644787 +2101,CASEY_COUNTY_KY,0.02139487441030218 +2101,TRIGG_COUNTY_KY,0.01887167235952945 +2101,UNION_COUNTY_KY,0.018344215760617916 +2101,WEBSTER_COUNTY_KY,0.01747048994410034 +2101,CALDWELL_COUNTY_KY,0.01697658656394908 +2101,TODD_COUNTY_KY,0.016431682291282203 +2101,WASHINGTON_COUNTY_KY,0.016141782481193422 +2101,MONROE_COUNTY_KY,0.015217055772160223 +2101,METCALFE_COUNTY_KY,0.013805136326727823 +2101,CLINTON_COUNTY_KY,0.012418717327553232 +2101,CRITTENDEN_COUNTY_KY,0.012065737466195133 +2101,LIVINGSTON_COUNTY_KY,0.011928840333653207 +2101,LYON_COUNTY_KY,0.011649677553567713 +2101,BALLARD_COUNTY_KY,0.010371970983176416 +2101,FULTON_COUNTY_KY,0.008743968808927841 +2101,CARLISLE_COUNTY_KY,0.006477113349483616 +2101,HICKMAN_COUNTY_KY,0.0060677640806082525 +2101,ANDERSON_COUNTY_KY,0.005827523034284679 +2102,WARREN_COUNTY_KY,0.17917312496338078 +2102,HARDIN_COUNTY_KY,0.14741162120558424 +2102,DAVIESS_COUNTY_KY,0.1375710412638554 +2102,BULLITT_COUNTY_KY,0.10948077957633573 +2102,BARREN_COUNTY_KY,0.05923656274801191 +2102,JEFFERSON_COUNTY_KY,0.042606115807247144 +2102,MUHLENBERG_COUNTY_KY,0.04118395892256968 +2102,MEADE_COUNTY_KY,0.039952221920391176 +2102,GRAYSON_COUNTY_KY,0.03518107199735809 +2102,OHIO_COUNTY_KY,0.031654975152202745 +2102,NELSON_COUNTY_KY,0.028949148570119793 +2102,BRECKINRIDGE_COUNTY_KY,0.027207405868660883 +2102,HART_COUNTY_KY,0.025684046808669297 +2102,LARUE_COUNTY_KY,0.01979700974204098 +2102,BUTLER_COUNTY_KY,0.01647331724751389 +2102,EDMONSON_COUNTY_KY,0.016147073392882825 +2102,GREEN_COUNTY_KY,0.014790165279131578 +2102,MCLEAN_COUNTY_KY,0.012186872479932673 +2102,HANCOCK_COUNTY_KY,0.012110970848447079 +2102,LOGAN_COUNTY_KY,0.003202516205664126 +2103,JEFFERSON_COUNTY_KY,1.0 +2104,KENTON_COUNTY_KY,0.22512660242112567 +2104,BOONE_COUNTY_KY,0.1810557769720083 +2104,CAMPBELL_COUNTY_KY,0.12394054113796368 +2104,OLDHAM_COUNTY_KY,0.09002587310063079 +2104,SHELBY_COUNTY_KY,0.06400363262061352 +2104,GREENUP_COUNTY_KY,0.0478872076625924 +2104,NELSON_COUNTY_KY,0.033287481707065365 +2104,GRANT_COUNTY_KY,0.03321158017665082 +2104,SPENCER_COUNTY_KY,0.025952996978586447 +2104,HARRISON_COUNTY_KY,0.024890375552782857 +2104,MASON_COUNTY_KY,0.022797091240297586 +2104,HENRY_COUNTY_KY,0.020876915681389344 +2104,PENDLETON_COUNTY_KY,0.019500035287553615 +2104,LEWIS_COUNTY_KY,0.017417403821442315 +2104,OWEN_COUNTY_KY,0.015017850175705385 +2104,CARROLL_COUNTY_KY,0.01439465866282809 +2104,GALLATIN_COUNTY_KY,0.01157165437372582 +2104,TRIMBLE_COUNTY_KY,0.011284027521628607 +2104,BRACKEN_COUNTY_KY,0.011185488692669377 +2104,CARTER_COUNTY_KY,0.00365259470047525 +2104,ROBERTSON_COUNTY_KY,0.002920211512264755 +2105,PULASKI_COUNTY_KY,0.08659965138560241 +2105,LAUREL_COUNTY_KY,0.08337583375167949 +2105,PIKE_COUNTY_KY,0.07812398048931186 +2105,BOYD_COUNTY_KY,0.06426462735677581 +2105,WHITLEY_COUNTY_KY,0.04888591201015216 +2105,FLOYD_COUNTY_KY,0.04786057554665747 +2105,KNOX_COUNTY_KY,0.040205173821162676 +2105,PERRY_COUNTY_KY,0.03791481185075895 +2105,HARLAN_COUNTY_KY,0.03572831513250144 +2105,ROWAN_COUNTY_KY,0.03284006215935859 +2105,LINCOLN_COUNTY_KY,0.032324730716017755 +2105,BELL_COUNTY_KY,0.032087704884196905 +2105,CARTER_COUNTY_KY,0.031804072849489926 +2105,JOHNSON_COUNTY_KY,0.030200819470207318 +2105,LETCHER_COUNTY_KY,0.028693441708290444 +2105,CLAY_COUNTY_KY,0.02709151993480458 +2105,WAYNE_COUNTY_KY,0.02603955135537496 +2105,MCCREARY_COUNTY_KY,0.022488158695452433 +2105,LAWRENCE_COUNTY_KY,0.021695853246388352 +2105,ROCKCASTLE_COUNTY_KY,0.021354962162421286 +2105,KNOTT_COUNTY_KY,0.018976714209432297 +2105,MORGAN_COUNTY_KY,0.01827762116614046 +2105,BREATHITT_COUNTY_KY,0.01826696831976649 +2105,JACKSON_COUNTY_KY,0.01725095309684902 +2105,MAGOFFIN_COUNTY_KY,0.015495896656737326 +2105,MARTIN_COUNTY_KY,0.015029834627876102 +2105,LESLIE_COUNTY_KY,0.013999171741194423 +2105,BATH_COUNTY_KY,0.011210789202807558 +2105,LEE_COUNTY_KY,0.009847224866939291 +2105,ELLIOTT_COUNTY_KY,0.00979262902927269 +2105,WOLFE_COUNTY_KY,0.008737997238249577 +2105,MENIFEE_COUNTY_KY,0.008140106235510465 +2105,OWSLEY_COUNTY_KY,0.005394335082619482 +2106,FAYETTE_COUNTY_KY,0.45177871148459386 +2106,MADISON_COUNTY_KY,0.12983333333333333 +2106,SCOTT_COUNTY_KY,0.08004901960784314 +2106,JESSAMINE_COUNTY_KY,0.0742170868347339 +2106,MONTGOMERY_COUNTY_KY,0.039375350140056026 +2106,WOODFORD_COUNTY_KY,0.0376344537815126 +2106,MERCER_COUNTY_KY,0.031710084033613445 +2106,BOURBON_COUNTY_KY,0.028364145658263305 +2106,ANDERSON_COUNTY_KY,0.027324929971988796 +2106,GARRARD_COUNTY_KY,0.023743697478991597 +2106,FLEMING_COUNTY_KY,0.021123249299719888 +2106,ESTILL_COUNTY_KY,0.019836134453781514 +2106,POWELL_COUNTY_KY,0.01838795518207283 +2106,NICHOLAS_COUNTY_KY,0.010556022408963585 +2106,BATH_COUNTY_KY,0.006065826330532213 +2201,ST_TAMMANY_PARISH_LA,0.3407971125569509 +2201,JEFFERSON_PARISH_LA,0.30925241554138916 +2201,ORLEANS_PARISH_LA,0.08307452916103653 +2201,TANGIPAHOA_PARISH_LA,0.0708683325454351 +2201,LAFOURCHE_PARISH_LA,0.060790105200514734 +2201,ASCENSION_PARISH_LA,0.03570402678252849 +2201,PLAQUEMINES_PARISH_LA,0.030290071065414446 +2201,ST_BERNARD_PARISH_LA,0.02646178736537567 +2201,ST_CHARLES_PARISH_LA,0.025616782618664558 +2201,LIVINGSTON_PARISH_LA,0.017144837162690465 +2202,ORLEANS_PARISH_LA,0.4115643629655965 +2202,JEFFERSON_PARISH_LA,0.2585287434498323 +2202,ASCENSION_PARISH_LA,0.08631665455819537 +2202,ST_JOHN_THE_BAPTIST_PARISH_LA,0.054716120754950304 +2202,ST_CHARLES_PARISH_LA,0.04207307333611571 +2202,IBERVILLE_PARISH_LA,0.038954497910644634 +2202,ST_BERNARD_PARISH_LA,0.029911788498498033 +2202,ASSUMPTION_PARISH_LA,0.027101077396317994 +2202,ST_JAMES_PARISH_LA,0.026010026844738483 +2202,LAFOURCHE_PARISH_LA,0.024823654285110703 +2203,LAFAYETTE_PARISH_LA,0.23240244909421387 +2203,CALCASIEU_PARISH_LA,0.1691371876638408 +2203,TERREBONNE_PARISH_LA,0.1411591331556499 +2203,IBERIA_PARISH_LA,0.09008137454317798 +2203,ACADIA_PARISH_LA,0.07416844543319674 +2203,VERMILION_PARISH_LA,0.07388890964295422 +2203,ST_MARTIN_PARISH_LA,0.06668538826490718 +2203,ST_MARY_PARISH_LA,0.06364398733973389 +2203,JEFFERSON_DAVIS_PARISH_LA,0.04154391352682706 +2203,LAFOURCHE_PARISH_LA,0.04005348537332198 +2203,CAMERON_PARISH_LA,0.00723572596217636 +2204,BOSSIER_PARISH_LA,0.1658452509461524 +2204,CADDO_PARISH_LA,0.1487063024441519 +2204,CALCASIEU_PARISH_LA,0.11011951534325559 +2204,OUACHITA_PARISH_LA,0.0713291992034028 +2204,VERNON_PARISH_LA,0.06279772562739759 +2204,LINCOLN_PARISH_LA,0.0623417175274571 +2204,WEBSTER_PARISH_LA,0.047619354323446286 +2204,BEAUREGARD_PARISH_LA,0.04708090408114368 +2204,EVANGELINE_PARISH_LA,0.041671926647103834 +2204,RAPIDES_PARISH_LA,0.0318419893288952 +2204,ALLEN_PARISH_LA,0.02930560529278554 +2204,GRANT_PARISH_LA,0.028557185219154402 +2204,SABINE_PARISH_LA,0.028539151000512686 +2204,UNION_PARISH_LA,0.02718916091933294 +2204,JACKSON_PARISH_LA,0.019362310028828985 +2204,CLAIBORNE_PARISH_LA,0.018253205582363565 +2204,WINN_PARISH_LA,0.01771861981548418 +2204,BIENVILLE_PARISH_LA,0.016721585156292268 +2204,DE_SOTO_PARISH_LA,0.01518352393784893 +2204,RED_RIVER_PARISH_LA,0.009815767574990145 +2205,EAST_BATON_ROUGE_PARISH_LA,0.2218244588005694 +2205,LIVINGSTON_PARISH_LA,0.1661400130106855 +2205,OUACHITA_PARISH_LA,0.1352531608880759 +2205,TANGIPAHOA_PARISH_LA,0.10065890748887328 +2205,WASHINGTON_PARISH_LA,0.05856483121533973 +2205,ASCENSION_PARISH_LA,0.040929555511184684 +2205,MOREHOUSE_PARISH_LA,0.03301493652460114 +2205,AVOYELLES_PARISH_LA,0.02592475701578673 +2205,RICHLAND_PARISH_LA,0.02581912570769756 +2205,FRANKLIN_PARISH_LA,0.025472603489697727 +2205,EAST_FELICIANA_PARISH_LA,0.02516987961895438 +2205,CONCORDIA_PARISH_LA,0.02407234456417424 +2205,WEST_FELICIANA_PARISH_LA,0.019722138132258128 +2205,LASALLE_PARISH_LA,0.01905356924325473 +2205,ST_HELENA_PARISH_LA,0.014066998589435581 +2205,MADISON_PARISH_LA,0.012903766013770716 +2205,WEST_CARROLL_PARISH_LA,0.012561108355822926 +2205,CALDWELL_PARISH_LA,0.012424560567317416 +2205,CATAHOULA_PARISH_LA,0.011472590607830888 +2205,EAST_CARROLL_PARISH_LA,0.009608584476062271 +2205,TENSAS_PARISH_LA,0.005342110178607084 +2206,EAST_BATON_ROUGE_PARISH_LA,0.36661599505307635 +2206,CADDO_PARISH_LA,0.1576922085952798 +2206,RAPIDES_PARISH_LA,0.13565907451303721 +2206,ST_LANDRY_PARISH_LA,0.10633309285787901 +2206,LAFAYETTE_PARISH_LA,0.07902452849634134 +2206,NATCHITOCHES_PARISH_LA,0.04832912501288261 +2206,WEST_BATON_ROUGE_PARISH_LA,0.035039420797691435 +2206,POINTE_COUPEE_PARISH_LA,0.026741729362052973 +2206,AVOYELLES_PARISH_LA,0.025208698340719364 +2206,DE_SOTO_PARISH_LA,0.019356126971039885 +2301,CUMBERLAND_COUNTY_ME,0.4449182960719576 +2301,YORK_COUNTY_ME,0.3111839912856973 +2301,KENNEBEC_COUNTY_ME,0.07867975965201511 +2301,KNOX_COUNTY_ME,0.059612818363455125 +2301,SAGADAHOC_COUNTY_ME,0.05387570667915482 +2301,LINCOLN_COUNTY_ME,0.05172942794772006 +2302,PENOBSCOT_COUNTY_ME,0.22343433453712674 +2302,ANDROSCOGGIN_COUNTY_ME,0.16315658122669485 +2302,KENNEBEC_COUNTY_ME,0.102831850612173 +2302,AROOSTOOK_COUNTY_ME,0.09851287471740215 +2302,OXFORD_COUNTY_ME,0.08481899057517836 +2302,HANCOCK_COUNTY_ME,0.08144396488446519 +2302,SOMERSET_COUNTY_ME,0.07410229307965589 +2302,WALDO_COUNTY_ME,0.05814469009659708 +2302,WASHINGTON_COUNTY_ME,0.04564872720866731 +2302,FRANKLIN_COUNTY_ME,0.0432426084148096 +2302,PISCATAQUIS_COUNTY_ME,0.024663084647229806 +2401,HARFORD_COUNTY_MD,0.3609536378400662 +2401,CECIL_COUNTY_MD,0.14348973680060426 +2401,WICOMICO_COUNTY_MD,0.14330021552857067 +2401,BALTIMORE_COUNTY_MD,0.07609763250580323 +2401,WORCESTER_COUNTY_MD,0.07257143015241938 +2401,TALBOT_COUNTY_MD,0.05191222813381032 +2401,CAROLINE_COUNTY_MD,0.04605643583805753 +2401,DORCHESTER_COUNTY_MD,0.045002310222805084 +2401,SOMERSET_COUNTY_MD,0.03405849428807787 +2401,KENT_COUNTY_MD,0.026557878689785496 +2402,BALTIMORE_COUNTY_MD,0.7623523490542297 +2402,CARROLL_COUNTY_MD,0.19836270569952758 +2402,BALTIMORE_CITY_MD,0.03928494524624273 +2403,ANNE_ARUNDEL_COUNTY_MD,0.5435092188619639 +2403,HOWARD_COUNTY_MD,0.430611364293091 +2403,CARROLL_COUNTY_MD,0.025879416844945078 +2404,MONTGOMERY_COUNTY_MD,1.0 +2405,ANNE_ARUNDEL_COUNTY_MD,0.39423236349794616 +2405,CHARLES_COUNTY_MD,0.3890947813842047 +2405,CALVERT_COUNTY_MD,0.21667285511784912 +2406,FREDERICK_COUNTY_MD,0.34932023732234574 +2406,MONTGOMERY_COUNTY_MD,0.32720014912996803 +2406,WASHINGTON_COUNTY_MD,0.19888923885864151 +2406,ALLEGANY_COUNTY_MD,0.08755728969139095 +2406,GARRETT_COUNTY_MD,0.03703308499765377 +2407,BALTIMORE_CITY_MD,0.723916809058882 +2407,BALTIMORE_COUNTY_MD,0.27608319094111805 +2408,MONTGOMERY_COUNTY_MD,1.0 +2501,HAMPDEN_COUNTY_MA,0.5963682126428909 +2501,BERKSHIRE_COUNTY_MA,0.1651843610893826 +2501,WORCESTER_COUNTY_MA,0.1368923176584906 +2501,HAMPSHIRE_COUNTY_MA,0.09889220755777407 +2501,FRANKLIN_COUNTY_MA,0.002662901051461843 +2502,WORCESTER_COUNTY_MA,0.7187137130793585 +2502,HAMPSHIRE_COUNTY_MA,0.10890140967685356 +2502,FRANKLIN_COUNTY_MA,0.0882715551509984 +2502,MIDDLESEX_COUNTY_MA,0.06732291982726946 +2502,NORFOLK_COUNTY_MA,0.01679040226552008 +2503,MIDDLESEX_COUNTY_MA,0.5534782909274333 +2503,ESSEX_COUNTY_MA,0.26883684845257083 +2503,WORCESTER_COUNTY_MA,0.17768486061999586 +2504,BRISTOL_COUNTY_MA,0.47399510691931795 +2504,NORFOLK_COUNTY_MA,0.3213545460713888 +2504,MIDDLESEX_COUNTY_MA,0.1194772008301082 +2504,WORCESTER_COUNTY_MA,0.07042093040226449 +2504,PLYMOUTH_COUNTY_MA,0.014752215776920586 +2505,MIDDLESEX_COUNTY_MA,0.8672527624468539 +2505,SUFFOLK_COUNTY_MA,0.1043424601940082 +2505,NORFOLK_COUNTY_MA,0.028404777359137934 +2506,ESSEX_COUNTY_MA,0.7679416618337449 +2506,MIDDLESEX_COUNTY_MA,0.23205833816625507 +2507,SUFFOLK_COUNTY_MA,0.6895245435295262 +2507,MIDDLESEX_COUNTY_MA,0.24977019646601853 +2507,NORFOLK_COUNTY_MA,0.060705260004455246 +2508,NORFOLK_COUNTY_MA,0.4914466996627841 +2508,PLYMOUTH_COUNTY_MA,0.2487882504461645 +2508,SUFFOLK_COUNTY_MA,0.22768473259574293 +2508,BRISTOL_COUNTY_MA,0.03208031729530842 +2509,PLYMOUTH_COUNTY_MA,0.41603657396857263 +2509,BARNSTABLE_COUNTY_MA,0.29317041820402456 +2509,BRISTOL_COUNTY_MA,0.23544044183730164 +2509,DUKES_COUNTY_MA,0.026372996100381255 +2509,NANTUCKET_COUNTY_MA,0.018249857252958 +2509,NORFOLK_COUNTY_MA,0.010729712636761908 +2601,GRAND_TRAVERSE_COUNTY_MI,0.12282830888279865 +2601,MARQUETTE_COUNTY_MI,0.08514202805094309 +2601,HOUGHTON_COUNTY_MI,0.04818442689021441 +2601,DELTA_COUNTY_MI,0.0475937449621151 +2601,CHIPPEWA_COUNTY_MI,0.04744156053522489 +2601,EMMET_COUNTY_MI,0.04399419635660164 +2601,ALPENA_COUNTY_MI,0.03728131549250363 +2601,CHARLEVOIX_COUNTY_MI,0.033601805577946155 +2601,DICKINSON_COUNTY_MI,0.03346380783491859 +2601,CHEBOYGAN_COUNTY_MI,0.03298919877478639 +2601,IOSCO_COUNTY_MI,0.03254812187651136 +2601,OTSEGO_COUNTY_MI,0.03235982589069805 +2601,MENOMINEE_COUNTY_MI,0.030310494921812026 +2601,ROSCOMMON_COUNTY_MI,0.030255037884894406 +2601,ANTRIM_COUNTY_MI,0.030218926325971306 +2601,LEELANAU_COUNTY_MI,0.028761566983717558 +2601,OGEMAW_COUNTY_MI,0.02678703852974367 +2601,BENZIE_COUNTY_MI,0.0231758826374335 +2601,KALKASKA_COUNTY_MI,0.023135901982911496 +2601,MISSAUKEE_COUNTY_MI,0.01941254231823311 +2601,ARENAC_COUNTY_MI,0.019348057391584718 +2601,GOGEBIC_COUNTY_MI,0.018545864904078672 +2601,CRAWFORD_COUNTY_MI,0.01675060454618733 +2601,PRESQUE_ISLE_COUNTY_MI,0.016742866354989522 +2601,IRON_COUNTY_MI,0.015000483636949863 +2601,MACKINAC_COUNTY_MI,0.013972593906174433 +2601,ALCONA_COUNTY_MI,0.01311236498468483 +2601,MONTMORENCY_COUNTY_MI,0.01180461067225536 +2601,ALGER_COUNTY_MI,0.011403514428502338 +2601,OSCODA_COUNTY_MI,0.010600032242463324 +2601,BARAGA_COUNTY_MI,0.010521360631952281 +2601,SCHOOLCRAFT_COUNTY_MI,0.010378204094792842 +2601,ONTONAGON_COUNTY_MI,0.007500886667741415 +2601,LUCE_COUNTY_MI,0.006885700467515718 +2601,WEXFORD_COUNTY_MI,0.005308399161695954 +2601,KEWEENAW_COUNTY_MI,0.0026387231984523617 +2602,KENT_COUNTY_MI,0.12752565493801912 +2602,IONIA_COUNTY_MI,0.08619904335113555 +2602,MONTCALM_COUNTY_MI,0.08595388111179786 +2602,MUSKEGON_COUNTY_MI,0.085697105924281 +2602,ISABELLA_COUNTY_MI,0.08308935389427313 +2602,BARRY_COUNTY_MI,0.08054611824303835 +2602,NEWAYGO_COUNTY_MI,0.06448799156641896 +2602,GRATIOT_COUNTY_MI,0.05388536987885115 +2602,MECOSTA_COUNTY_MI,0.05124406933188128 +2602,CLARE_COUNTY_MI,0.039814347668442586 +2602,WEXFORD_COUNTY_MI,0.038138212147917995 +2602,MASON_COUNTY_MI,0.03748659672230989 +2602,OCEANA_COUNTY_MI,0.03439884283423032 +2602,GLADWIN_COUNTY_MI,0.032756255830667734 +2602,MANISTEE_COUNTY_MI,0.03229947986895433 +2602,OSCEOLA_COUNTY_MI,0.029536888529891083 +2602,LAKE_COUNTY_MI,0.015607802352783301 +2602,MIDLAND_COUNTY_MI,0.012452951430779732 +2602,EATON_COUNTY_MI,0.006175507776159134 +2602,OTTAWA_COUNTY_MI,0.002704526598167477 +2603,KENT_COUNTY_MI,0.7210883476439682 +2603,MUSKEGON_COUNTY_MI,0.1410975298356749 +2603,OTTAWA_COUNTY_MI,0.13781412252035688 +2604,KALAMAZOO_COUNTY_MI,0.31542989930286597 +2604,OTTAWA_COUNTY_MI,0.24172605215595147 +2604,ALLEGAN_COUNTY_MI,0.15556674412600052 +2604,CALHOUN_COUNTY_MI,0.11326620191066357 +2604,VAN_BUREN_COUNTY_MI,0.09758197779499096 +2604,BERRIEN_COUNTY_MI,0.0764291247095275 +2605,JACKSON_COUNTY_MI,0.2070456939825239 +2605,MONROE_COUNTY_MI,0.1970101117560784 +2605,LENAWEE_COUNTY_MI,0.12836326922679667 +2605,BERRIEN_COUNTY_MI,0.12279999586853685 +2605,ST_JOSEPH_COUNTY_MI,0.07867726042678014 +2605,CASS_COUNTY_MI,0.06660564151294181 +2605,CALHOUN_COUNTY_MI,0.060130864095519425 +2605,HILLSDALE_COUNTY_MI,0.05906184800347043 +2605,BRANCH_COUNTY_MI,0.05792053130616208 +2605,KALAMAZOO_COUNTY_MI,0.022384783821190274 +2606,WASHTENAW_COUNTY_MI,0.48016376166847036 +2606,WAYNE_COUNTY_MI,0.43628502475902037 +2606,OAKLAND_COUNTY_MI,0.08069286560992063 +2606,MONROE_COUNTY_MI,0.0028583479625886622 +2607,INGHAM_COUNTY_MI,0.3675000451474257 +2607,LIVINGSTON_COUNTY_MI,0.25007288084433427 +2607,EATON_COUNTY_MI,0.13465413202139215 +2607,CLINTON_COUNTY_MI,0.10206930000851351 +2607,SHIAWASSEE_COUNTY_MI,0.08783625157693509 +2607,OAKLAND_COUNTY_MI,0.05760553533237535 +2607,GENESEE_COUNTY_MI,0.00026185506902396424 +2608,GENESEE_COUNTY_MI,0.5237265375779286 +2608,SAGINAW_COUNTY_MI,0.24524882325093617 +2608,BAY_COUNTY_MI,0.13396815650601307 +2608,MIDLAND_COUNTY_MI,0.09525314455470577 +2608,TUSCOLA_COUNTY_MI,0.001803338110416406 +2609,OAKLAND_COUNTY_MI,0.26670081887886116 +2609,MACOMB_COUNTY_MI,0.2520562298538509 +2609,ST_CLAIR_COUNTY_MI,0.20695595396935593 +2609,LAPEER_COUNTY_MI,0.11435270374547397 +2609,TUSCOLA_COUNTY_MI,0.06700328532237709 +2609,SANILAC_COUNTY_MI,0.05240385980215804 +2609,HURON_COUNTY_MI,0.04052714842792292 +2610,MACOMB_COUNTY_MI,0.8847614477476013 +2610,OAKLAND_COUNTY_MI,0.11523855225239868 +2611,OAKLAND_COUNTY_MI,1.0 +2612,WAYNE_COUNTY_MI,0.8766947824370813 +2612,OAKLAND_COUNTY_MI,0.12330521756291865 +2613,WAYNE_COUNTY_MI,1.0 +2701,OLMSTED_COUNTY_MN,0.228297334542717 +2701,BLUE_EARTH_COUNTY_MN,0.0968890147495272 +2701,WINONA_COUNTY_MN,0.0696344231338084 +2701,GOODHUE_COUNTY_MN,0.06670582677121199 +2701,MOWER_COUNTY_MN,0.056117177500417066 +2701,STEELE_COUNTY_MN,0.052439959568827625 +2701,NICOLLET_COUNTY_MN,0.048301512243607625 +2701,FREEBORN_COUNTY_MN,0.043312103696704525 +2701,RICE_COUNTY_MN,0.04323640039197489 +2701,NOBLES_COUNTY_MN,0.03124864189673228 +2701,BROWN_COUNTY_MN,0.030840685199022586 +2701,WABASHA_COUNTY_MN,0.029982714412086733 +2701,FILLMORE_COUNTY_MN,0.02975981023704948 +2701,DODGE_COUNTY_MN,0.02925371962580137 +2701,MARTIN_COUNTY_MN,0.028073308837239297 +2701,WASECA_COUNTY_MN,0.02659148674280924 +2701,HOUSTON_COUNTY_MN,0.02641624761149064 +2701,FARIBAULT_COUNTY_MN,0.01951603157668955 +2701,WATONWAN_COUNTY_MN,0.015775727557825407 +2701,JACKSON_COUNTY_MN,0.014003709461931753 +2701,ROCK_COUNTY_MN,0.01360416424252535 +2702,DAKOTA_COUNTY_MN,0.6166754519761338 +2702,SCOTT_COUNTY_MN,0.21158763626575747 +2702,WASHINGTON_COUNTY_MN,0.08071082499663541 +2702,RICE_COUNTY_MN,0.050827688304696965 +2702,LE_SUEUR_COUNTY_MN,0.04019839845677628 +2703,HENNEPIN_COUNTY_MN,0.8835991594129349 +2703,ANOKA_COUNTY_MN,0.11640084058706511 +2704,RAMSEY_COUNTY_MN,0.7692384258220807 +2704,WASHINGTON_COUNTY_MN,0.23076157417791934 +2705,HENNEPIN_COUNTY_MN,0.9114707168812525 +2705,ANOKA_COUNTY_MN,0.08341931721322507 +2705,RAMSEY_COUNTY_MN,0.005109965905522408 +2706,ANOKA_COUNTY_MN,0.3103172244403571 +2706,WRIGHT_COUNTY_MN,0.19814190704768742 +2706,CARVER_COUNTY_MN,0.1498951370508277 +2706,STEARNS_COUNTY_MN,0.14582258534834686 +2706,SHERBURNE_COUNTY_MN,0.1362419249921493 +2706,BENTON_COUNTY_MN,0.05800967879413216 +2706,HENNEPIN_COUNTY_MN,0.0015715423264994841 +2707,CLAY_COUNTY_MN,0.09157002826252748 +2707,OTTER_TAIL_COUNTY_MN,0.08422821990938047 +2707,STEARNS_COUNTY_MN,0.07608872414875958 +2707,KANDIYOHI_COUNTY_MN,0.061308375577587365 +2707,DOUGLAS_COUNTY_MN,0.05468294378897313 +2707,MCLEOD_COUNTY_MN,0.051549672513570496 +2707,MORRISON_COUNTY_MN,0.04767899600735723 +2707,POLK_COUNTY_MN,0.043728410569288056 +2707,BECKER_COUNTY_MN,0.04372280292494729 +2707,LYON_COUNTY_MN,0.03542489121169979 +2707,TODD_COUNTY_MN,0.03541507783410345 +2707,MEEKER_COUNTY_MN,0.03280471939347719 +2707,REDWOOD_COUNTY_MN,0.02162447848907631 +2707,ROSEAU_COUNTY_MN,0.021492698847068323 +2707,SIBLEY_COUNTY_MN,0.020798752859898614 +2707,RENVILLE_COUNTY_MN,0.020640336907271992 +2707,WADENA_COUNTY_MN,0.019717879413216097 +2707,PENNINGTON_COUNTY_MN,0.01961553990399713 +2707,CHIPPEWA_COUNTY_MN,0.017661275851240412 +2707,HUBBARD_COUNTY_MN,0.017344443945987168 +2707,COTTONWOOD_COUNTY_MN,0.01614580996814858 +2707,POPE_COUNTY_MN,0.01585281055134359 +2707,SWIFT_COUNTY_MN,0.013792001256112333 +2707,STEVENS_COUNTY_MN,0.01355788210488538 +2707,YELLOW_MEDICINE_COUNTY_MN,0.013357408819703018 +2707,PIPESTONE_COUNTY_MN,0.01321161006684312 +2707,MARSHALL_COUNTY_MN,0.012673276210129649 +2707,MURRAY_COUNTY_MN,0.01146623076577991 +2707,LAC_QUI_PARLE_COUNTY_MN,0.009419440581400566 +2707,WILKIN_COUNTY_MN,0.009120833520254812 +2707,NORMAN_COUNTY_MN,0.009029709299717375 +2707,GRANT_COUNTY_MN,0.008515207931452155 +2707,LINCOLN_COUNTY_MN,0.007906778520479118 +2707,BIG_STONE_COUNTY_MN,0.007242272666098426 +2707,KITTSON_COUNTY_MN,0.005897839935399937 +2707,RED_LAKE_COUNTY_MN,0.005516520120227895 +2707,BROWN_COUNTY_MN,0.0054856780763536854 +2707,TRAVERSE_COUNTY_MN,0.004710421246242878 +2708,ST_LOUIS_COUNTY_MN,0.28070605849894575 +2708,CROW_WING_COUNTY_MN,0.0926985666861065 +2708,CHISAGO_COUNTY_MN,0.07937760755461845 +2708,BELTRAMI_COUNTY_MN,0.06480754564622493 +2708,WASHINGTON_COUNTY_MN,0.06363414606791978 +2708,ITASCA_COUNTY_MN,0.06310562558880266 +2708,ISANTI_COUNTY_MN,0.057667612489345474 +2708,CARLTON_COUNTY_MN,0.05075899466152259 +2708,CASS_COUNTY_MN,0.04214985868736261 +2708,PINE_COUNTY_MN,0.04048158449598493 +2708,MILLE_LACS_COUNTY_MN,0.03709316540307748 +2708,KANABEC_COUNTY_MN,0.02247543851778745 +2708,AITKIN_COUNTY_MN,0.022005798304248352 +2708,KOOCHICHING_COUNTY_MN,0.016909851509577856 +2708,LAKE_COUNTY_MN,0.015287840384011485 +2708,HUBBARD_COUNTY_MN,0.012577946256336637 +2708,CLEARWATER_COUNTY_MN,0.01194989009017092 +2708,COOK_COUNTY_MN,0.007850702077071463 +2708,MAHNOMEN_COUNTY_MN,0.007585740881970302 +2708,BECKER_COUNTY_MN,0.005600634785339374 +2708,LAKE_OF_THE_WOODS_COUNTY_MN,0.005275391413574986 +2801,DESOTO_COUNTY_MS,0.25313595775284703 +2801,LEE_COUNTY_MS,0.11384520396189997 +2801,LOWNDES_COUNTY_MS,0.080427771547373 +2801,LAFAYETTE_COUNTY_MS,0.07623966462360994 +2801,ALCORN_COUNTY_MS,0.0474542839307009 +2801,MONROE_COUNTY_MS,0.04668933289439715 +2801,MARSHALL_COUNTY_MS,0.04610469174522213 +2801,PONTOTOC_COUNTY_MS,0.042596844850172046 +2801,TATE_COUNTY_MS,0.0383349747907654 +2801,UNION_COUNTY_MS,0.037942937384659724 +2801,PRENTISS_COUNTY_MS,0.03416052770693633 +2801,ITAWAMBA_COUNTY_MS,0.03259647603449383 +2801,TIPPAH_COUNTY_MS,0.029798940816011518 +2801,TISHOMINGO_COUNTY_MS,0.025748798275581807 +2801,CLAY_COUNTY_MS,0.0254564777009943 +2801,CHICKASAW_COUNTY_MS,0.023366522191092965 +2801,CALHOUN_COUNTY_MS,0.018121143656438635 +2801,WEBSTER_COUNTY_MS,0.013558757118484085 +2801,BENTON_COUNTY_MS,0.010444313613533077 +2801,OKTIBBEHA_COUNTY_MS,0.003976379404786135 +2802,HINDS_COUNTY_MS,0.2868675442928272 +2802,WASHINGTON_COUNTY_MS,0.0632009015435134 +2802,WARREN_COUNTY_MS,0.06291952092135271 +2802,PANOLA_COUNTY_MS,0.04672043850356158 +2802,BOLIVAR_COUNTY_MS,0.043592892888245464 +2802,COPIAH_COUNTY_MS,0.03991102744727279 +2802,LEFLORE_COUNTY_MS,0.039870227257059486 +2802,YAZOO_COUNTY_MS,0.03762480989221715 +2802,SUNFLOWER_COUNTY_MS,0.036538680690676874 +2802,MADISON_COUNTY_MS,0.035406123686480086 +2802,GRENADA_COUNTY_MS,0.030429907383568214 +2802,COAHOMA_COUNTY_MS,0.030093657540086187 +2802,LEAKE_COUNTY_MS,0.02993186368234379 +2802,ATTALA_COUNTY_MS,0.025168089749163243 +2802,HOLMES_COUNTY_MS,0.02391735288365896 +2802,AMITE_COUNTY_MS,0.01789580756942012 +2802,TALLAHATCHIE_COUNTY_MS,0.0178887730538661 +2802,YALOBUSHA_COUNTY_MS,0.017559557725938087 +2802,CARROLL_COUNTY_MS,0.014066217301813075 +2802,MONTGOMERY_COUNTY_MS,0.013818602354311666 +2802,TUNICA_COUNTY_MS,0.013762326229879527 +2802,CLAIBORNE_COUNTY_MS,0.012852059917189683 +2802,WILKINSON_COUNTY_MS,0.012081077012469382 +2802,HUMPHREYS_COUNTY_MS,0.010952740717605 +2802,FRANKLIN_COUNTY_MS,0.01079798137541662 +2802,JEFFERSON_COUNTY_MS,0.01021411658443318 +2802,QUITMAN_COUNTY_MS,0.00868903361232222 +2802,SHARKEY_COUNTY_MS,0.005346231821053179 +2802,ISSAQUENA_COUNTY_MS,0.0018824363622550406 +2803,RANKIN_COUNTY_MS,0.22518279943041433 +2803,MADISON_COUNTY_MS,0.1204260707335925 +2803,LAUDERDALE_COUNTY_MS,0.10465921654723818 +2803,OKTIBBEHA_COUNTY_MS,0.07008972551763894 +2803,PIKE_COUNTY_MS,0.057824704703097016 +2803,LINCOLN_COUNTY_MS,0.05005671478700048 +2803,SCOTT_COUNTY_MS,0.04013772157126489 +2803,SIMPSON_COUNTY_MS,0.037210923081556005 +2803,MARION_COUNTY_MS,0.03504844776431887 +2803,HINDS_COUNTY_MS,0.034189480446663006 +2803,NEWTON_COUNTY_MS,0.03053134083507684 +2803,COVINGTON_COUNTY_MS,0.026299600343586926 +2803,WINSTON_COUNTY_MS,0.02540191496653756 +2803,JASPER_COUNTY_MS,0.02347031400346168 +2803,CLARKE_COUNTY_MS,0.022391944349242632 +2803,SMITH_COUNTY_MS,0.020375737256380953 +2803,LAWRENCE_COUNTY_MS,0.017230970432308642 +2803,JEFFERSON_DAVIS_COUNTY_MS,0.01623433890347588 +2803,JONES_COUNTY_MS,0.015600509931182234 +2803,NOXUBEE_COUNTY_MS,0.014748712624525167 +2803,KEMPER_COUNTY_MS,0.012888811771437258 +2804,HARRISON_COUNTY_MS,0.28179803085418353 +2804,JACKSON_COUNTY_MS,0.19349984668812584 +2804,FORREST_COUNTY_MS,0.1055731230101537 +2804,LAMAR_COUNTY_MS,0.08674885623938805 +2804,JONES_COUNTY_MS,0.07613859393425285 +2804,PEARL_RIVER_COUNTY_MS,0.07583872401296195 +2804,HANCOCK_COUNTY_MS,0.062206799482927 +2804,GEORGE_COUNTY_MS,0.03289113776321353 +2804,WAYNE_COUNTY_MS,0.02671678906852568 +2804,STONE_COUNTY_MS,0.024763582283901173 +2804,GREENE_COUNTY_MS,0.018275856013810224 +2804,PERRY_COUNTY_MS,0.015548660648556504 +2901,ST_LOUIS_COUNTY_MO,0.6080164915436647 +2901,ST_LOUIS_CITY_MO,0.39198350845633534 +2902,ST_LOUIS_COUNTY_MO,0.6971199588231318 +2902,ST_CHARLES_COUNTY_MO,0.13610332690378027 +2902,FRANKLIN_COUNTY_MO,0.13606303388253155 +2902,WARREN_COUNTY_MO,0.030713680390556355 +2903,ST_CHARLES_COUNTY_MO,0.3906460401058537 +2903,BOONE_COUNTY_MO,0.13458389007023983 +2903,COLE_COUNTY_MO,0.10044530287354231 +2903,JEFFERSON_COUNTY_MO,0.0886706422447632 +2903,CALLAWAY_COUNTY_MO,0.05755793096635663 +2903,MILLER_COUNTY_MO,0.03213303455841448 +2903,WASHINGTON_COUNTY_MO,0.030562906504593406 +2903,CRAWFORD_COUNTY_MO,0.029967609610015544 +2903,COOPER_COUNTY_MO,0.02223004975538237 +2903,MONITEAU_COUNTY_MO,0.020111416702627106 +2903,GASCONADE_COUNTY_MO,0.019228869559792243 +2903,OSAGE_COUNTY_MO,0.017253211743725988 +2903,WARREN_COUNTY_MO,0.01546992060975039 +2903,CAMDEN_COUNTY_MO,0.015463421735355436 +2903,MONTGOMERY_COUNTY_MO,0.014716051179935635 +2903,MARIES_COUNTY_MO,0.010959701779651764 +2904,CASS_COUNTY_MO,0.14014692655232114 +2904,BOONE_COUNTY_MO,0.1040677754612901 +2904,JACKSON_COUNTY_MO,0.10094441642707483 +2904,JOHNSON_COUNTY_MO,0.07020474053893866 +2904,PULASKI_COUNTY_MO,0.07012935359595718 +2904,PETTIS_COUNTY_MO,0.05586432429903141 +2904,LACLEDE_COUNTY_MO,0.04684258686395516 +2904,LAFAYETTE_COUNTY_MO,0.04287177460863779 +2904,POLK_COUNTY_MO,0.04096760441091603 +2904,CAMDEN_COUNTY_MO,0.04009545546711309 +2904,SALINE_COUNTY_MO,0.030327647251496042 +2904,HENRY_COUNTY_MO,0.028524859494335583 +2904,WEBSTER_COUNTY_MO,0.028420877504016305 +2904,MORGAN_COUNTY_MO,0.02730307110808408 +2904,VERNON_COUNTY_MO,0.025614663540274824 +2904,BENTON_COUNTY_MO,0.025207834003150655 +2904,DALLAS_COUNTY_MO,0.022188456959254657 +2904,BATES_COUNTY_MO,0.02085098860877296 +2904,CEDAR_COUNTY_MO,0.018441205983123723 +2904,BARTON_COUNTY_MO,0.015125480266817787 +2904,HOWARD_COUNTY_MO,0.013194014796637222 +2904,ST_CLAIR_COUNTY_MO,0.012067109976552061 +2904,HICKORY_COUNTY_MO,0.010760836223166148 +2904,DADE_COUNTY_MO,0.009837996059082567 +2905,JACKSON_COUNTY_MO,0.81396842066954 +2905,CLAY_COUNTY_MO,0.18603157933045997 +2906,CLAY_COUNTY_MO,0.14324688963871457 +2906,PLATTE_COUNTY_MO,0.13870937553615714 +2906,BUCHANAN_COUNTY_MO,0.11021181131428037 +2906,LINCOLN_COUNTY_MO,0.07743278864100737 +2906,MARION_COUNTY_MO,0.0370760784232171 +2906,ADAIR_COUNTY_MO,0.03290250128677713 +2906,AUDRAIN_COUNTY_MO,0.032444980529372315 +2906,RANDOLPH_COUNTY_MO,0.032125235909140536 +2906,RAY_COUNTY_MO,0.03010018664767262 +2906,NODAWAY_COUNTY_MO,0.027608518204646954 +2906,CLINTON_COUNTY_MO,0.02753443103654447 +2906,ANDREW_COUNTY_MO,0.02357141743050104 +2906,PIKE_COUNTY_MO,0.022859140796813993 +2906,MACON_COUNTY_MO,0.019768276134573493 +2906,LIVINGSTON_COUNTY_MO,0.018920822913471386 +2906,JACKSON_COUNTY_MO,0.017290905215216725 +2906,LINN_COUNTY_MO,0.015433526913138644 +2906,DEKALB_COUNTY_MO,0.014335217140391284 +2906,RALLS_COUNTY_MO,0.013459168871951378 +2906,LEWIS_COUNTY_MO,0.013039341586037298 +2906,GRUNDY_COUNTY_MO,0.012748192013143323 +2906,CALDWELL_COUNTY_MO,0.011457515558305301 +2906,MONROE_COUNTY_MO,0.01126384910133565 +2906,CARROLL_COUNTY_MO,0.011041587597028195 +2906,DAVIESS_COUNTY_MO,0.010957102229893783 +2906,HARRISON_COUNTY_MO,0.01060226368792925 +2906,CHARITON_COUNTY_MO,0.009628732303565023 +2906,CLARK_COUNTY_MO,0.00862270654722602 +2906,GENTRY_COUNTY_MO,0.008009212804342287 +2906,SHELBY_COUNTY_MO,0.007932526086481822 +2906,SULLIVAN_COUNTY_MO,0.007797349499066761 +2906,ATCHISON_COUNTY_MO,0.006895305733047036 +2906,SCOTLAND_COUNTY_MO,0.006129738329321362 +2906,PUTNAM_COUNTY_MO,0.006084246208556678 +2906,HOLT_COUNTY_MO,0.0054889493139788186 +2906,SCHUYLER_COUNTY_MO,0.005240692312091546 +2906,KNOX_COUNTY_MO,0.004866357146942149 +2906,MERCER_COUNTY_MO,0.004598603521870012 +2906,WORTH_COUNTY_MO,0.0025644558362491617 +2907,GREENE_COUNTY_MO,0.388521702962833 +2907,JASPER_COUNTY_MO,0.15956145652583625 +2907,CHRISTIAN_COUNTY_MO,0.11547444970852586 +2907,NEWTON_COUNTY_MO,0.0762290980223951 +2907,TANEY_COUNTY_MO,0.07287308364690362 +2907,LAWRENCE_COUNTY_MO,0.049392680977169486 +2907,BARRY_COUNTY_MO,0.044886367328901104 +2907,STONE_COUNTY_MO,0.04039175163933893 +2907,MCDONALD_COUNTY_MO,0.030288614636745887 +2907,WEBSTER_COUNTY_MO,0.02238079455135079 +2908,JEFFERSON_COUNTY_MO,0.20603901404276778 +2908,CAPE_GIRARDEAU_COUNTY_MO,0.10620460536235124 +2908,ST_FRANCOIS_COUNTY_MO,0.08698353445183295 +2908,PHELPS_COUNTY_MO,0.058019351048398414 +2908,BUTLER_COUNTY_MO,0.05475951565188909 +2908,HOWELL_COUNTY_MO,0.05166605143989061 +2908,SCOTT_COUNTY_MO,0.0494681321195169 +2908,STODDARD_COUNTY_MO,0.03726714533042877 +2908,DUNKLIN_COUNTY_MO,0.03676153290250129 +2908,TEXAS_COUNTY_MO,0.03182758746185161 +2908,PERRY_COUNTY_MO,0.024638532606152615 +2908,STE_GENEVIEVE_COUNTY_MO,0.024018539988873926 +2908,WRIGHT_COUNTY_MO,0.023640305499087556 +2908,NEW_MADRID_COUNTY_MO,0.021360500361337416 +2908,PEMISCOT_COUNTY_MO,0.020355774379877404 +2908,DENT_COUNTY_MO,0.018744053529928615 +2908,MADISON_COUNTY_MO,0.016410957622139847 +2908,MISSISSIPPI_COUNTY_MO,0.016347268653069288 +2908,DOUGLAS_COUNTY_MO,0.01504879354895732 +2908,WAYNE_COUNTY_MO,0.014263729522046782 +2908,RIPLEY_COUNTY_MO,0.013880295932744448 +2908,BOLLINGER_COUNTY_MO,0.013734721146297462 +2908,IRON_COUNTY_MO,0.012395953020936774 +2908,OREGON_COUNTY_MO,0.01122355608008693 +2908,OZARK_COUNTY_MO,0.01111697454000967 +2908,SHANNON_COUNTY_MO,0.009138717174185431 +2908,REYNOLDS_COUNTY_MO,0.007923427662328885 +2908,CARTER_COUNTY_MO,0.006761428920510968 +3001,GALLATIN_COUNTY_MT,0.2194380497019066 +3001,MISSOULA_COUNTY_MT,0.21752331621509946 +3001,FLATHEAD_COUNTY_MT,0.19250081164039903 +3001,RAVALLI_COUNTY_MT,0.08148500678826516 +3001,SILVER_BOW_COUNTY_MT,0.06480764122542944 +3001,LAKE_COUNTY_MT,0.05743093678059146 +3001,LINCOLN_COUNTY_MT,0.03629692757216221 +3001,GLACIER_COUNTY_MT,0.02541541231332271 +3001,SANDERS_COUNTY_MT,0.022873502154536332 +3001,DEER_LODGE_COUNTY_MT,0.01737832772563603 +3001,BEAVERHEAD_COUNTY_MT,0.017286095862109673 +3001,MADISON_COUNTY_MT,0.015906307183755387 +3001,POWELL_COUNTY_MT,0.0128128504810814 +3001,MINERAL_COUNTY_MT,0.008365430021840505 +3001,GRANITE_COUNTY_MT,0.006103904728174252 +3001,PONDERA_COUNTY_MT,0.004375479605690337 +3002,YELLOWSTONE_COUNTY_MT,0.3038683816842614 +3002,CASCADE_COUNTY_MT,0.15571292332041475 +3002,LEWIS_AND_CLARK_COUNTY_MT,0.1309191995026867 +3002,PARK_COUNTY_MT,0.03171110082215332 +3002,HILL_COUNTY_MT,0.03008413375071249 +3002,BIG_HORN_COUNTY_MT,0.02420897488162062 +3002,JEFFERSON_COUNTY_MT,0.02229240029292786 +3002,CUSTER_COUNTY_MT,0.021890270109737268 +3002,RICHLAND_COUNTY_MT,0.021196687775426895 +3002,FERGUS_COUNTY_MT,0.02111367925137379 +3002,ROOSEVELT_COUNTY_MT,0.019910977969537717 +3002,CARBON_COUNTY_MT,0.019318850497958912 +3002,STILLWATER_COUNTY_MT,0.016533453357510334 +3002,DAWSON_COUNTY_MT,0.01649102677854986 +3002,ROSEBUD_COUNTY_MT,0.0153639554852955 +3002,VALLEY_COUNTY_MT,0.0139786354505426 +3002,BLAINE_COUNTY_MT,0.012993600965112439 +3002,BROADWATER_COUNTY_MT,0.01249554982079382 +3002,TETON_COUNTY_MT,0.011484690461213806 +3002,CHOUTEAU_COUNTY_MT,0.010874116650956535 +3002,TOOLE_COUNTY_MT,0.009169674957066146 +3002,MUSSELSHELL_COUNTY_MT,0.008725118194915083 +3002,PHILLIPS_COUNTY_MT,0.007778821020709705 +3002,SWEET_GRASS_COUNTY_MT,0.0067845633659403114 +3002,SHERIDAN_COUNTY_MT,0.0065281592583096145 +3002,PONDERA_COUNTY_MT,0.006504179018027607 +3002,FALLON_COUNTY_MT,0.0056242886630647115 +3002,WHEATLAND_COUNTY_MT,0.003816547472574906 +3002,JUDITH_BASIN_COUNTY_MT,0.003731694314653956 +3002,LIBERTY_COUNTY_MT,0.0036136377471117647 +3002,MEAGHER_COUNTY_MT,0.003554609463340669 +3002,MCCONE_COUNTY_MT,0.003189371957507014 +3002,POWDER_RIVER_COUNTY_MT,0.003124809772132378 +3002,DANIELS_COUNTY_MT,0.003063936854493436 +3002,CARTER_COUNTY_MT,0.0026101569230031377 +3002,GARFIELD_COUNTY_MT,0.0021637555269842265 +3002,PRAIRIE_COUNTY_MT,0.0020069616482172536 +3002,WIBAUX_COUNTY_MT,0.0017284219341723959 +3002,GOLDEN_VALLEY_COUNTY_MT,0.0015181336732378674 +3002,TREASURE_COUNTY_MT,0.0014056110072992163 +3002,PETROLEUM_COUNTY_MT,0.0009149383984519832 +3101,LANCASTER_COUNTY_NE,0.49341869805543404 +3101,SARPY_COUNTY_NE,0.21957199360070478 +3101,DODGE_COUNTY_NE,0.056845747007595336 +3101,MADISON_COUNTY_NE,0.05442612821226572 +3101,PLATTE_COUNTY_NE,0.052454643618599556 +3101,CASS_COUNTY_NE,0.04068079691414483 +3101,SEWARD_COUNTY_NE,0.026932406679493807 +3101,COLFAX_COUNTY_NE,0.01618483318089633 +3101,CUMING_COUNTY_NE,0.013785097473012533 +3101,BUTLER_COUNTY_NE,0.01280011991031198 +3101,STANTON_COUNTY_NE,0.008935153604497861 +3101,POLK_COUNTY_NE,0.003964381743043213 +3102,DOUGLAS_COUNTY_NE,0.8939797842614556 +3102,SARPY_COUNTY_NE,0.0719480245378506 +3102,SAUNDERS_COUNTY_NE,0.03407219120069374 +3103,HALL_COUNTY_NE,0.09657121887480212 +3103,BUFFALO_COUNTY_NE,0.07690075405239827 +3103,SCOTTS_BLUFF_COUNTY_NE,0.05540465636184688 +3103,LINCOLN_COUNTY_NE,0.053242763108397144 +3103,ADAMS_COUNTY_NE,0.047913266316689726 +3103,DAWSON_COUNTY_NE,0.03702088652977747 +3103,GAGE_COUNTY_NE,0.03332509316255196 +3103,DAKOTA_COUNTY_NE,0.033137770025534294 +3103,WASHINGTON_COUNTY_NE,0.0320368627366682 +3103,OTOE_COUNTY_NE,0.02443185046086098 +3103,SALINE_COUNTY_NE,0.02194444487095432 +3103,YORK_COUNTY_NE,0.02168802713421703 +3103,BOX_BUTTE_COUNTY_NE,0.016647192225782727 +3103,RED_WILLOW_COUNTY_NE,0.016432231248877213 +3103,CUSTER_COUNTY_NE,0.016191167867633173 +3103,HOLT_COUNTY_NE,0.01554935580801528 +3103,WAYNE_COUNTY_NE,0.014889118521805488 +3103,CHEYENNE_COUNTY_NE,0.01453750378101004 +3103,HAMILTON_COUNTY_NE,0.014477621794586362 +3103,PHELPS_COUNTY_NE,0.013769786006347491 +3103,KNOX_COUNTY_NE,0.012883839694386908 +3103,CEDAR_COUNTY_NE,0.012866949903344332 +3103,KEITH_COUNTY_NE,0.012797855303624702 +3103,DAWES_COUNTY_NE,0.012589036068916488 +3103,RICHARDSON_COUNTY_NE,0.012085413208737857 +3103,MERRICK_COUNTY_NE,0.011773719792224862 +3103,PIERCE_COUNTY_NE,0.011234781914411752 +3103,JEFFERSON_COUNTY_NE,0.011116553377113718 +3103,NEMAHA_COUNTY_NE,0.010861671075925752 +3103,THURSTON_COUNTY_NE,0.010399504975578898 +3103,BURT_COUNTY_NE,0.010321197762563317 +3103,KEARNEY_COUNTY_NE,0.010268992953886264 +3103,HOWARD_COUNTY_NE,0.009941945181880019 +3103,ANTELOPE_COUNTY_NE,0.009665566783001501 +3103,CLAY_COUNTY_NE,0.009372298593080405 +3103,DIXON_COUNTY_NE,0.008607651689516507 +3103,FILLMORE_COUNTY_NE,0.008523202734303625 +3103,CHERRY_COUNTY_NE,0.008375800921568416 +3103,BOONE_COUNTY_NE,0.008259107819819709 +3103,JOHNSON_COUNTY_NE,0.008122454055929776 +3103,SHERIDAN_COUNTY_NE,0.007872178061389784 +3103,THAYER_COUNTY_NE,0.0077293825553025495 +3103,FURNAS_COUNTY_NE,0.00711827920667116 +3103,MORRILL_COUNTY_NE,0.0069939089271758275 +3103,NUCKOLLS_COUNTY_NE,0.0062876085744862815 +3103,VALLEY_COUNTY_NE,0.006232332894710578 +3103,CHASE_COUNTY_NE,0.005977450593522612 +3103,KIMBALL_COUNTY_NE,0.005272685676382391 +3103,WEBSTER_COUNTY_NE,0.005212803689958712 +3103,NANCE_COUNTY_NE,0.0051897721567188354 +3103,HARLAN_COUNTY_NE,0.00471839344307603 +3103,SHERMAN_COUNTY_NE,0.004543353790452969 +3103,BROWN_COUNTY_NE,0.004457369399690763 +3103,FRANKLIN_COUNTY_NE,0.004435873302000212 +3103,PERKINS_COUNTY_NE,0.004388274799971134 +3103,POLK_COUNTY_NE,0.0040259120103304106 +3103,HITCHCOCK_COUNTY_NE,0.0040166993970344594 +3103,PAWNEE_COUNTY_NE,0.0039061480374830526 +3103,FRONTIER_COUNTY_NE,0.003867762148749925 +3103,GREELEY_COUNTY_NE,0.003359532981923317 +3103,GOSPER_COUNTY_NE,0.002906579494872413 +3103,GARDEN_COUNTY_NE,0.002877406219435236 +3103,GARFIELD_COUNTY_NE,0.002783744650926405 +3103,BOYD_COUNTY_NE,0.0027791383442784296 +3103,DUNDY_COUNTY_NE,0.002539610398583714 +3103,ROCK_COUNTY_NE,0.0019377196632482754 +3103,SIOUX_COUNTY_NE,0.0017427193484839878 +3103,HAYES_COUNTY_NE,0.001314332830222285 +3103,WHEELER_COUNTY_NE,0.001188427115177627 +3103,KEYA_PAHA_COUNTY_NE,0.0011807499374310013 +3103,HOOKER_COUNTY_NE,0.0010916946755701457 +3103,BANNER_COUNTY_NE,0.001034883560245117 +3103,THOMAS_COUNTY_NE,0.0010272063824984913 +3103,GRANT_COUNTY_NE,0.0009381511206376356 +3103,LOUP_COUNTY_NE,0.0009320093784403353 +3103,ARTHUR_COUNTY_NE,0.0006663790284070931 +3103,BLAINE_COUNTY_NE,0.0006617727217591178 +3103,MCPHERSON_COUNTY_NE,0.0006126387841807146 +3201,CLARK_COUNTY_NV,1.0 +3202,WASHOE_COUNTY_NV,0.6470151695300718 +3202,LYON_COUNTY_NV,0.07828679801357091 +3202,CARSON_CITY_NV,0.07798755688906267 +3202,ELKO_COUNTY_NV,0.07142154163707505 +3202,DOUGLAS_COUNTY_NV,0.06581708786517391 +3202,HUMBOLDT_COUNTY_NV,0.022988368165000227 +3202,WHITE_PINE_COUNTY_NV,0.012076041824599482 +3202,PERSHING_COUNTY_NV,0.008844237679910414 +3202,LANDER_COUNTY_NV,0.0076259938130235055 +3202,STOREY_COUNTY_NV,0.0054581581110304265 +3202,EUREKA_COUNTY_NV,0.002467076826501326 +3202,LINCOLN_COUNTY_NV,1.1969644980329884e-05 +3203,CLARK_COUNTY_NV,1.0 +3204,CLARK_COUNTY_NV,0.9206632748133688 +3204,NYE_COUNTY_NV,0.06630049387063237 +3204,MINERAL_COUNTY_NV,0.0058524248238425265 +3204,LINCOLN_COUNTY_NV,0.00577017730765326 +3204,ESMERALDA_COUNTY_NV,0.000936850614093369 +3204,LYON_COUNTY_NV,0.00047677857040965687 +3301,ROCKINGHAM_COUNTY_NH,0.3654183435835787 +3301,HILLSBOROUGH_COUNTY_NH,0.26739928335394997 +3301,STRAFFORD_COUNTY_NH,0.19003461272656527 +3301,BELKNAP_COUNTY_NH,0.08753070717981776 +3301,CARROLL_COUNTY_NH,0.06802620346011115 +3301,MERRIMACK_COUNTY_NH,0.021590849695977143 +3302,HILLSBOROUGH_COUNTY_NH,0.3466523415098038 +3302,MERRIMACK_COUNTY_NH,0.20171901882354648 +3302,GRAFTON_COUNTY_NH,0.13229185571276125 +3302,CHESHIRE_COUNTY_NH,0.11100738277932241 +3302,ROCKINGHAM_COUNTY_NH,0.09072615478428782 +3302,SULLIVAN_COUNTY_NH,0.0625220503364718 +3302,COOS_COUNTY_NH,0.04539719643129369 +3302,BELKNAP_COUNTY_NH,0.004961053479779025 +3302,CARROLL_COUNTY_NH,0.004722946142733733 +3401,CAMDEN_COUNTY_NJ,0.6783143114815275 +3401,GLOUCESTER_COUNTY_NJ,0.28615836339511547 +3401,BURLINGTON_COUNTY_NJ,0.03552732512335697 +3402,ATLANTIC_COUNTY_NJ,0.35239315273869914 +3402,CUMBERLAND_COUNTY_NJ,0.19787024296071143 +3402,OCEAN_COUNTY_NJ,0.13967802057875173 +3402,CAPE_MAY_COUNTY_NJ,0.12228004148610626 +3402,GLOUCESTER_COUNTY_NJ,0.10455345957158407 +3402,SALEM_COUNTY_NJ,0.08322508266414738 +3403,BURLINGTON_COUNTY_NJ,0.558391514914707 +3403,MERCER_COUNTY_NJ,0.22723592074280485 +3403,MONMOUTH_COUNTY_NJ,0.21437256434248814 +3404,OCEAN_COUNTY_NJ,0.6836134437474206 +3404,MONMOUTH_COUNTY_NJ,0.31638655625257933 +3405,BERGEN_COUNTY_NJ,0.8180720055663474 +3405,SUSSEX_COUNTY_NJ,0.10990302864134048 +3405,PASSAIC_COUNTY_NJ,0.07202496579231213 +3406,MIDDLESEX_COUNTY_NJ,0.6998182917877194 +3406,MONMOUTH_COUNTY_NJ,0.3001817082122807 +3407,UNION_COUNTY_NJ,0.25867306217018343 +3407,SOMERSET_COUNTY_NJ,0.203360738944122 +3407,HUNTERDON_COUNTY_NJ,0.16653493375901143 +3407,MORRIS_COUNTY_NJ,0.15333064701521645 +3407,WARREN_COUNTY_NJ,0.14158962874567024 +3407,SUSSEX_COUNTY_NJ,0.07651098936579645 +3408,HUDSON_COUNTY_NJ,0.6737129806335338 +3408,UNION_COUNTY_NJ,0.17776287407783356 +3408,ESSEX_COUNTY_NJ,0.1485241452886326 +3409,PASSAIC_COUNTY_NJ,0.5029984716213766 +3409,BERGEN_COUNTY_NJ,0.4185995907053856 +3409,HUDSON_COUNTY_NJ,0.07840193767323783 +3410,ESSEX_COUNTY_NJ,0.5770823853059198 +3410,UNION_COUNTY_NJ,0.23679828734538536 +3410,HUDSON_COUNTY_NJ,0.18611932734869482 +3411,MORRIS_COUNTY_NJ,0.5050830053940251 +3411,ESSEX_COUNTY_NJ,0.3913532653185268 +3411,PASSAIC_COUNTY_NJ,0.10356372928744813 +3412,MIDDLESEX_COUNTY_NJ,0.41521448413313095 +3412,MERCER_COUNTY_NJ,0.27177757468736125 +3412,SOMERSET_COUNTY_NJ,0.24254675382343527 +3412,UNION_COUNTY_NJ,0.07046118735607257 +3501,BERNALILLO_COUNTY_NM,0.6889670629838262 +3501,SANDOVAL_COUNTY_NM,0.18234509061646398 +3501,VALENCIA_COUNTY_NM,0.04794767026714572 +3501,LINCOLN_COUNTY_NM,0.02871646510784436 +3501,TORRANCE_COUNTY_NM,0.021315270489294904 +3501,SANTA_FE_COUNTY_NM,0.013528715048340115 +3501,GUADALUPE_COUNTY_NM,0.006307449931428442 +3501,CHAVES_COUNTY_NM,0.005620317582654229 +3501,OTERO_COUNTY_NM,0.0028462863684276143 +3501,DE_BACA_COUNTY_NM,0.00240567160457446 +3502,BERNALILLO_COUNTY_NM,0.3910237823498566 +3502,OTERO_COUNTY_NM,0.13537328932621817 +3502,EDDY_COUNTY_NM,0.09323133553368909 +3502,VALENCIA_COUNTY_NM,0.08711352396228549 +3502,GRANT_COUNTY_NM,0.05795983836639008 +3502,CIBOLA_COUNTY_NM,0.05587669782123652 +3502,LUNA_COUNTY_NM,0.05228826716843003 +3502,LEA_COUNTY_NM,0.03914988124248126 +3502,SOCORRO_COUNTY_NM,0.03412607832855219 +3502,SIERRA_COUNTY_NM,0.023804970336325407 +3502,MCKINLEY_COUNTY_NM,0.013763533730219933 +3502,HIDALGO_COUNTY_NM,0.008591669494226637 +3502,CATRON_COUNTY_NM,0.007359881550942349 +3502,CHAVES_COUNTY_NM,0.00033725078914628254 +3503,SANTA_FE_COUNTY_NM,0.21163856211530757 +3503,SAN_JUAN_COUNTY_NM,0.1772385912517755 +3503,MCKINLEY_COUNTY_NM,0.09645482026441345 +3503,CHAVES_COUNTY_NM,0.08890410459991988 +3503,LEA_COUNTY_NM,0.08073278216848162 +3503,CURRY_COUNTY_NM,0.07055395709655098 +3503,RIO_ARRIBA_COUNTY_NM,0.05880176275630987 +3503,TAOS_COUNTY_NM,0.05024438212477692 +3503,SAN_MIGUEL_COUNTY_NM,0.03962705321047456 +3503,SANDOVAL_COUNTY_NM,0.029324398149834285 +3503,ROOSEVELT_COUNTY_NM,0.02795789780383873 +3503,EDDY_COUNTY_NM,0.02473249080380231 +3503,COLFAX_COUNTY_NM,0.0180456714134829 +3503,QUAY_COUNTY_NM,0.012741377426521469 +3503,MORA_COUNTY_NM,0.0061026332082893256 +3503,UNION_COUNTY_NM,0.0059423826346651125 +3503,HARDING_COUNTY_NM,0.0009571329715555232 +3601,SUFFOLK_COUNTY_NY,1.0 +3602,SUFFOLK_COUNTY_NY,0.9019808976139393 +3602,NASSAU_COUNTY_NY,0.09801910238606074 +3603,NASSAU_COUNTY_NY,0.698410880200162 +3603,QUEENS_COUNTY_NY,0.23963571355945074 +3603,SUFFOLK_COUNTY_NY,0.06195340624038735 +3604,NASSAU_COUNTY_NY,1.0 +3605,QUEENS_COUNTY_NY,1.0 +3606,QUEENS_COUNTY_NY,1.0 +3607,KINGS_COUNTY_NY,0.6036292170116813 +3607,QUEENS_COUNTY_NY,0.39637078298831874 +3608,KINGS_COUNTY_NY,1.0 +3609,KINGS_COUNTY_NY,1.0 +3610,KINGS_COUNTY_NY,0.555882322657702 +3610,NEW_YORK_COUNTY_NY,0.44411767734229807 +3611,RICHMOND_COUNTY_NY,0.6380508410223805 +3611,KINGS_COUNTY_NY,0.3619491589776195 +3612,NEW_YORK_COUNTY_NY,1.0 +3613,NEW_YORK_COUNTY_NY,0.7364663546001073 +3613,BRONX_COUNTY_NY,0.26353364539989266 +3614,BRONX_COUNTY_NY,0.5400568876533199 +3614,QUEENS_COUNTY_NY,0.45994311234668006 +3615,BRONX_COUNTY_NY,1.0 +3616,WESTCHESTER_COUNTY_NY,0.9082116215256149 +3616,BRONX_COUNTY_NY,0.09178837847438517 +3617,ROCKLAND_COUNTY_NY,0.43544611060129657 +3617,WESTCHESTER_COUNTY_NY,0.3845729119877061 +3617,PUTNAM_COUNTY_NY,0.12570353333650805 +3617,DUTCHESS_COUNTY_NY,0.05427744407448927 +3618,ORANGE_COUNTY_NY,0.5165057640503957 +3618,DUTCHESS_COUNTY_NY,0.3265746083187146 +3618,ULSTER_COUNTY_NY,0.1569196276308897 +3619,BROOME_COUNTY_NY,0.2557148207590759 +3619,TOMPKINS_COUNTY_NY,0.13609259547653646 +3619,SULLIVAN_COUNTY_NY,0.1011929660180367 +3619,RENSSELAER_COUNTY_NY,0.08266717805426457 +3619,COLUMBIA_COUNTY_NY,0.07924362685351191 +3619,ULSTER_COUNTY_NY,0.0771315789134987 +3619,OTSEGO_COUNTY_NY,0.07532327461385303 +3619,GREENE_COUNTY_NY,0.06168956112905115 +3619,CHENANGO_COUNTY_NY,0.060774469059977784 +3619,DELAWARE_COUNTY_NY,0.05702658142968013 +3619,CORTLAND_COUNTY_NY,0.01314334769251362 +3620,ALBANY_COUNTY_NY,0.40522490543405093 +3620,SARATOGA_COUNTY_NY,0.23259426671008313 +3620,SCHENECTADY_COUNTY_NY,0.2034323031361531 +3620,RENSSELAER_COUNTY_NY,0.12471507945598999 +3620,MONTGOMERY_COUNTY_NY,0.034033445263722845 +3621,ST_LAWRENCE_COUNTY_NY,0.13965128685626618 +3621,ONEIDA_COUNTY_NY,0.11484727229201605 +3621,CLINTON_COUNTY_NY,0.10276187914349441 +3621,WARREN_COUNTY_NY,0.08460676138491656 +3621,WASHINGTON_COUNTY_NY,0.07889869763478946 +3621,HERKIMER_COUNTY_NY,0.07740185927145286 +3621,SARATOGA_COUNTY_NY,0.07051743243956338 +3621,FULTON_COUNTY_NY,0.06863061813117864 +3621,FRANKLIN_COUNTY_NY,0.06120563058338085 +3621,ESSEX_COUNTY_NY,0.048111190765163694 +3621,JEFFERSON_COUNTY_NY,0.04462200004890787 +3621,SCHOHARIE_COUNTY_NY,0.03824338360118975 +3621,LEWIS_COUNTY_NY,0.034212345119702024 +3621,MONTGOMERY_COUNTY_NY,0.029716681832397864 +3621,HAMILTON_COUNTY_NY,0.006572960895580402 +3622,ONONDAGA_COUNTY_NY,0.6132995954803976 +3622,ONEIDA_COUNTY_NY,0.18390905194659776 +3622,MADISON_COUNTY_NY,0.08753994679338097 +3622,CAYUGA_COUNTY_NY,0.06814926168415553 +3622,CORTLAND_COUNTY_NY,0.047102144095468174 +3623,ERIE_COUNTY_NY,0.36270594397989114 +3623,CHAUTAUQUA_COUNTY_NY,0.16430085550168538 +3623,CHEMUNG_COUNTY_NY,0.10830262648155466 +3623,STEUBEN_COUNTY_NY,0.10216726235599527 +3623,CATTARAUGUS_COUNTY_NY,0.09915685398811538 +3623,TIOGA_COUNTY_NY,0.062363974974612955 +3623,ALLEGANY_COUNTY_NY,0.05979116337675409 +3623,NIAGARA_COUNTY_NY,0.028795154516706542 +3623,SCHUYLER_COUNTY_NY,0.012416164824684576 +3624,OSWEGO_COUNTY_NY,0.15126047175505908 +3624,ONTARIO_COUNTY_NY,0.12217959228851527 +3624,WAYNE_COUNTY_NY,0.11748572340537806 +3624,NIAGARA_COUNTY_NY,0.11035933130065344 +3624,JEFFERSON_COUNTY_NY,0.10560368404998385 +3624,LIVINGSTON_COUNTY_NY,0.07958340787493999 +3624,GENESEE_COUNTY_NY,0.07514823590584462 +3624,WYOMING_COUNTY_NY,0.052165396134476065 +3624,ORLEANS_COUNTY_NY,0.05192343086164091 +3624,SENECA_COUNTY_NY,0.043520285827913785 +3624,YATES_COUNTY_NY,0.03188535994264908 +3624,CAYUGA_COUNTY_NY,0.02998567514102843 +3624,STEUBEN_COUNTY_NY,0.01827996154296621 +3624,SCHUYLER_COUNTY_NY,0.010619443968951222 +3625,MONROE_COUNTY_NY,0.977440599456093 +3625,ONTARIO_COUNTY_NY,0.022559400543907045 +3626,ERIE_COUNTY_NY,0.8654428543665079 +3626,NIAGARA_COUNTY_NY,0.1345571456334921 +3701,WAYNE_COUNTY_NC,0.157352448133893 +3701,NASH_COUNTY_NC,0.12736196977215122 +3701,WILSON_COUNTY_NC,0.10565531669505276 +3701,LENOIR_COUNTY_NC,0.07392278085480172 +3701,EDGECOMBE_COUNTY_NC,0.06557860715866268 +3701,HALIFAX_COUNTY_NC,0.06520578808320035 +3701,VANCE_COUNTY_NC,0.057100325881422075 +3701,PASQUOTANK_COUNTY_NC,0.05440476350128073 +3701,CURRITUCK_COUNTY_NC,0.03768423028953827 +3701,MARTIN_COUNTY_NC,0.02954524119248461 +3701,HERTFORD_COUNTY_NC,0.02890286587900814 +3701,GREENE_COUNTY_NC,0.02742634141107997 +3701,WARREN_COUNTY_NC,0.025000335268952754 +3701,BERTIE_COUNTY_NC,0.024050853594753712 +3701,NORTHAMPTON_COUNTY_NC,0.02342993549425349 +3701,CHOWAN_COUNTY_NC,0.0183834672174018 +3701,PERQUIMANS_COUNTY_NC,0.017440690922257834 +3701,WASHINGTON_COUNTY_NC,0.01475585714860461 +3701,GRANVILLE_COUNTY_NC,0.014507758123566725 +3701,GATES_COUNTY_NC,0.014051792347821423 +3701,CAMDEN_COUNTY_NC,0.013886840023066505 +3701,TYRRELL_COUNTY_NC,0.004351791006745612 +3702,WAKE_COUNTY_NC,1.0 +3703,ONSLOW_COUNTY_NC,0.2743515571880897 +3703,PITT_COUNTY_NC,0.2283084631157709 +3703,CRAVEN_COUNTY_NC,0.13507297454239203 +3703,CARTERET_COUNTY_NC,0.09077193561235451 +3703,DUPLIN_COUNTY_NC,0.06533042052057811 +3703,SAMPSON_COUNTY_NC,0.06185972097614095 +3703,BEAUFORT_COUNTY_NC,0.05988163680765378 +3703,DARE_COUNTY_NC,0.04950574717268071 +3703,PAMLICO_COUNTY_NC,0.016463024577863427 +3703,JONES_COUNTY_NC,0.012300330842958892 +3703,HYDE_COUNTY_NC,0.0061541886435170475 +3704,DURHAM_COUNTY_NC,0.43562509471335215 +3704,WAKE_COUNTY_NC,0.31046131604957145 +3704,ORANGE_COUNTY_NC,0.19941234136770775 +3704,CHATHAM_COUNTY_NC,0.05450124786936866 +3705,GUILFORD_COUNTY_NC,0.3541508252298936 +3705,ROCKINGHAM_COUNTY_NC,0.1221664782457679 +3705,CALDWELL_COUNTY_NC,0.10816030125886618 +3705,SURRY_COUNTY_NC,0.09569770046039071 +3705,WILKES_COUNTY_NC,0.08846931153283419 +3705,WATAUGA_COUNTY_NC,0.0725333290418965 +3705,STOKES_COUNTY_NC,0.059704615037999335 +3705,ALEXANDER_COUNTY_NC,0.04887410131277735 +3705,ASHE_COUNTY_NC,0.035641724031107554 +3705,ALLEGHANY_COUNTY_NC,0.014601613848466683 +3706,GUILFORD_COUNTY_NC,0.23824045725259532 +3706,DAVIDSON_COUNTY_NC,0.22654763293731417 +3706,ROWAN_COUNTY_NC,0.1969702455908839 +3706,FORSYTH_COUNTY_NC,0.14516723863473302 +3706,CABARRUS_COUNTY_NC,0.13579447236113515 +3706,DAVIE_COUNTY_NC,0.05727995322333844 +3707,NEW_HANOVER_COUNTY_NC,0.3026830867768761 +3707,CUMBERLAND_COUNTY_NC,0.2570396327602924 +3707,BRUNSWICK_COUNTY_NC,0.183315429995266 +3707,PENDER_COUNTY_NC,0.080736678776565 +3707,COLUMBUS_COUNTY_NC,0.06788918973649237 +3707,ROBESON_COUNTY_NC,0.05132022031163878 +3707,BLADEN_COUNTY_NC,0.03970383721507206 +3707,SAMPSON_COUNTY_NC,0.017311924427797245 +3708,UNION_COUNTY_NC,0.3195336817443618 +3708,CABARRUS_COUNTY_NC,0.16702540396501941 +3708,MECKLENBURG_COUNTY_NC,0.1571282777525209 +3708,ROBESON_COUNTY_NC,0.10495513436891069 +3708,STANLY_COUNTY_NC,0.083822490079405 +3708,RICHMOND_COUNTY_NC,0.057593764542271324 +3708,SCOTLAND_COUNTY_NC,0.0458298633043259 +3708,MONTGOMERY_COUNTY_NC,0.03453399689675474 +3708,ANSON_COUNTY_NC,0.029577387346430263 +3709,ALAMANCE_COUNTY_NC,0.22988020185846036 +3709,RANDOLPH_COUNTY_NC,0.19334398146099285 +3709,CUMBERLAND_COUNTY_NC,0.19185538930708046 +3709,MOORE_COUNTY_NC,0.13374128804794608 +3709,GUILFORD_COUNTY_NC,0.1335307394279783 +3709,HOKE_COUNTY_NC,0.069845816720779 +3709,CHATHAM_COUNTY_NC,0.04780258317676294 +3710,FORSYTH_COUNTY_NC,0.36791476122145184 +3710,IREDELL_COUNTY_NC,0.2503694663859348 +3710,CATAWBA_COUNTY_NC,0.215390186007215 +3710,LINCOLN_COUNTY_NC,0.11641879115426396 +3710,YADKIN_COUNTY_NC,0.049906795231134414 +3711,BUNCOMBE_COUNTY_NC,0.36135555942977454 +3711,HENDERSON_COUNTY_NC,0.1559416363807046 +3711,HAYWOOD_COUNTY_NC,0.08326605603014739 +3711,MCDOWELL_COUNTY_NC,0.05978247750345327 +3711,JACKSON_COUNTY_NC,0.057812437137071356 +3711,MACON_COUNTY_NC,0.0496385800689313 +3711,TRANSYLVANIA_COUNTY_NC,0.04423672670216047 +3711,CHEROKEE_COUNTY_NC,0.03858811538616278 +3711,MADISON_COUNTY_NC,0.028421419662853543 +3711,YANCEY_COUNTY_NC,0.02476967022945807 +3711,AVERY_COUNTY_NC,0.023879195890943714 +3711,MITCHELL_COUNTY_NC,0.019986052811565437 +3711,SWAIN_COUNTY_NC,0.018931967224107178 +3711,CLAY_COUNTY_NC,0.014871189668351952 +3711,GRAHAM_COUNTY_NC,0.010768838762455242 +3711,POLK_COUNTY_NC,0.007750077111859133 +3712,MECKLENBURG_COUNTY_NC,1.0 +3713,JOHNSTON_COUNTY_NC,0.2896710341035579 +3713,WAKE_COUNTY_NC,0.20416269931739242 +3713,HARNETT_COUNTY_NC,0.1791248139257312 +3713,FRANKLIN_COUNTY_NC,0.09196159158877251 +3713,LEE_COUNTY_NC,0.08486998270012204 +3713,GRANVILLE_COUNTY_NC,0.06728713774189655 +3713,PERSON_COUNTY_NC,0.052432040983276786 +3713,CASWELL_COUNTY_NC,0.030490699639250607 +3714,MECKLENBURG_COUNTY_NC,0.33881698497058355 +3714,GASTON_COUNTY_NC,0.3056884336389641 +3714,CLEVELAND_COUNTY_NC,0.13346234465333906 +3714,BURKE_COUNTY_NC,0.11743785127757415 +3714,RUTHERFORD_COUNTY_NC,0.08642417366372032 +3714,POLK_COUNTY_NC,0.0181702117958188 +3800,CASS_COUNTY_ND,0.2368456181154007 +3800,BURLEIGH_COUNTY_ND,0.1263749945449458 +3800,GRAND_FORKS_COUNTY_ND,0.09391678026014832 +3800,WARD_COUNTY_ND,0.08974398467964072 +3800,WILLIAMS_COUNTY_ND,0.052561051683108845 +3800,STARK_COUNTY_ND,0.04318605970524738 +3800,MORTON_COUNTY_ND,0.04273040223644387 +3800,STUTSMAN_COUNTY_ND,0.027715525982744058 +3800,RICHLAND_COUNTY_ND,0.021215668455924444 +3800,MCKENZIE_COUNTY_ND,0.018873204003624723 +3800,ROLETTE_COUNTY_ND,0.015642528372699572 +3800,RAMSEY_COUNTY_ND,0.014895506832294948 +3800,BARNES_COUNTY_ND,0.013930283123730897 +3800,WALSH_COUNTY_ND,0.01355805589569423 +3800,MOUNTRAIL_COUNTY_ND,0.012590265102798892 +3800,MCLEAN_COUNTY_ND,0.012541490500504432 +3800,MERCER_COUNTY_ND,0.010717577083124758 +3800,TRAILL_COUNTY_ND,0.010264486698652537 +3800,PEMBINA_COUNTY_ND,0.00878456258166537 +3800,BOTTINEAU_COUNTY_ND,0.008187715474641057 +3800,BENSON_COUNTY_ND,0.007655045475898929 +3800,RANSOM_COUNTY_ND,0.007320040970665928 +3800,MCHENRY_COUNTY_ND,0.006860532875365488 +3800,DICKEY_COUNTY_ND,0.006416427286052774 +3800,DUNN_COUNTY_ND,0.005256105168310884 +3800,LAMOURE_COUNTY_ND,0.005253538083979597 +3800,PIERCE_COUNTY_ND,0.005121333240918297 +3800,WELLS_COUNTY_ND,0.005111064903593148 +3800,SIOUX_COUNTY_ND,0.005003247361679079 +3800,SARGENT_COUNTY_ND,0.004957039843715906 +3800,CAVALIER_COUNTY_ND,0.004754240181544204 +3800,FOSTER_COUNTY_ND,0.004360192736691593 +3800,EMMONS_COUNTY_ND,0.0042369726887898 +3800,NELSON_COUNTY_ND,0.003869879629415706 +3800,BOWMAN_COUNTY_ND,0.0038416417017715448 +3800,MCINTOSH_COUNTY_ND,0.0032473616790785194 +3800,HETTINGER_COUNTY_ND,0.0031947364502871284 +3800,KIDDER_COUNTY_ND,0.0030727999445509784 +3800,EDDY_COUNTY_ND,0.0030124734627657254 +3800,GRIGGS_COUNTY_ND,0.0029598482339743344 +3800,GRANT_COUNTY_ND,0.0029534305231461158 +3800,RENVILLE_COUNTY_ND,0.002929043221998886 +3800,BURKE_COUNTY_ND,0.0028250763065817476 +3800,ADAMS_COUNTY_ND,0.0028237927644161036 +3800,DIVIDE_COUNTY_ND,0.0028173750535878854 +3800,TOWNER_COUNTY_ND,0.002775018162121644 +3800,OLIVER_COUNTY_ND,0.002409208644913194 +3800,LOGAN_COUNTY_ND,0.0024079251027475505 +3800,STEELE_COUNTY_ND,0.002307808813827343 +3800,GOLDEN_VALLEY_COUNTY_ND,0.002228229199557435 +3800,SHERIDAN_COUNTY_ND,0.0016236808395392597 +3800,BILLINGS_COUNTY_ND,0.001212947346533281 +3800,SLOPE_COUNTY_ND,0.0009061807689444406 +3901,HAMILTON_COUNTY_OH,0.6919301323366767 +3901,WARREN_COUNTY_OH,0.3080698676633233 +3902,CLERMONT_COUNTY_OH,0.26518346005550264 +3902,ROSS_COUNTY_OH,0.09800426884846605 +3902,SCIOTO_COUNTY_OH,0.09408247089797096 +3902,PICKAWAY_COUNTY_OH,0.07441754626386772 +3902,LAWRENCE_COUNTY_OH,0.07403744331826058 +3902,BROWN_COUNTY_OH,0.05552299749945654 +3902,HIGHLAND_COUNTY_OH,0.055066619715266024 +3902,CLINTON_COUNTY_OH,0.05341526945993601 +3902,JACKSON_COUNTY_OH,0.04151003840438123 +3902,GALLIA_COUNTY_OH,0.03714584639010258 +3902,HOCKING_COUNTY_OH,0.0356584870377268 +3902,ADAMS_COUNTY_OH,0.03493006232925559 +3902,PIKE_COUNTY_OH,0.034435547125773396 +3902,MEIGS_COUNTY_OH,0.028234402748945182 +3902,VINTON_COUNTY_OH,0.016271965564452873 +3902,FAYETTE_COUNTY_OH,0.0020835743406358016 +3903,FRANKLIN_COUNTY_OH,1.0 +3904,DELAWARE_COUNTY_OH,0.20925721114119725 +3904,RICHLAND_COUNTY_OH,0.15882435198250766 +3904,ALLEN_COUNTY_OH,0.12992893736572467 +3904,MARION_COUNTY_OH,0.08308734729161105 +3904,UNION_COUNTY_OH,0.07981388963044887 +3904,ASHLAND_COUNTY_OH,0.06667302289513494 +3904,AUGLAIZE_COUNTY_OH,0.059013767590862284 +3904,LOGAN_COUNTY_OH,0.05866798876218807 +3904,CHAMPAIGN_COUNTY_OH,0.0492150057841679 +3904,MORROW_COUNTY_OH,0.04443003699324968 +3904,HARDIN_COUNTY_OH,0.03902215781244041 +3904,SHELBY_COUNTY_OH,0.02070477861256245 +3904,WYANDOT_COUNTY_OH,0.001361504137904733 +3905,LORAIN_COUNTY_OH,0.39785413726911 +3905,HANCOCK_COUNTY_OH,0.09524172736864854 +3905,WOOD_COUNTY_OH,0.08883464907262627 +3905,HURON_COUNTY_OH,0.07445050404891754 +3905,SENECA_COUNTY_OH,0.0700062291038989 +3905,MERCER_COUNTY_OH,0.05406353685976889 +3905,CRAWFORD_COUNTY_OH,0.053424100275860316 +3905,PUTNAM_COUNTY_OH,0.04379568539211574 +3905,VAN_WERT_COUNTY_OH,0.03677840916313896 +3905,HENRY_COUNTY_OH,0.0351651983778905 +3905,WYANDOT_COUNTY_OH,0.026478776553144425 +3905,PAULDING_COUNTY_OH,0.02390704651487993 +3906,MAHONING_COUNTY_OH,0.2906245630092928 +3906,STARK_COUNTY_OH,0.16699464805562972 +3906,COLUMBIANA_COUNTY_OH,0.1295106975325121 +3906,BELMONT_COUNTY_OH,0.08453402489099068 +3906,JEFFERSON_COUNTY_OH,0.08294751026530897 +3906,TUSCARAWAS_COUNTY_OH,0.08206526575391226 +3906,WASHINGTON_COUNTY_OH,0.07598362635546572 +3906,CARROLL_COUNTY_OH,0.033968956180160936 +3906,HARRISON_COUNTY_OH,0.018411451381208446 +3906,NOBLE_COUNTY_OH,0.017943632965943326 +3906,MONROE_COUNTY_OH,0.017015623609575023 +3907,CUYAHOGA_COUNTY_OH,0.6078931645119052 +3907,MEDINA_COUNTY_OH,0.23196420172126667 +3907,WAYNE_COUNTY_OH,0.14860099411413244 +3907,HOLMES_COUNTY_OH,0.011541639652695676 +3908,BUTLER_COUNTY_OH,0.49624028608149456 +3908,HAMILTON_COUNTY_OH,0.364016582149908 +3908,DARKE_COUNTY_OH,0.06595358167573277 +3908,PREBLE_COUNTY_OH,0.05211986845132839 +3908,MIAMI_COUNTY_OH,0.021669681641536227 +3909,LUCAS_COUNTY_OH,0.5482615715139265 +3909,ERIE_COUNTY_OH,0.0961341418455945 +3909,WOOD_COUNTY_OH,0.07928505142188831 +3909,SANDUSKY_COUNTY_OH,0.0748712863735174 +3909,FULTON_COUNTY_OH,0.0542987173130951 +3909,OTTAWA_COUNTY_OH,0.05131256117869901 +3909,DEFIANCE_COUNTY_OH,0.04867091262728347 +3909,WILLIAMS_COUNTY_OH,0.047165757725995704 +3910,MONTGOMERY_COUNTY_OH,0.6830517524121887 +3910,GREENE_COUNTY_OH,0.21352605418049148 +3910,CLARK_COUNTY_OH,0.10342219340731983 +3911,CUYAHOGA_COUNTY_OH,1.0 +3912,LICKING_COUNTY_OH,0.22694150998563492 +3912,FAIRFIELD_COUNTY_OH,0.20202763688138006 +3912,MUSKINGUM_COUNTY_OH,0.10984834038874693 +3912,KNOX_COUNTY_OH,0.07973380115174859 +3912,ATHENS_COUNTY_OH,0.07936513990058859 +3912,DELAWARE_COUNTY_OH,0.06294700176703151 +3912,GUERNSEY_COUNTY_OH,0.048864141972719065 +3912,COSHOCTON_COUNTY_OH,0.04654284733610465 +3912,PERRY_COUNTY_OH,0.045012267520943776 +3912,HOLMES_COUNTY_OH,0.044676658657818794 +3912,TUSCARAWAS_COUNTY_OH,0.03649492137345385 +3912,MORGAN_COUNTY_OH,0.017545733063829245 +3913,SUMMIT_COUNTY_OH,0.6870167677306993 +3913,STARK_COUNTY_OH,0.30953561394810775 +3913,PORTAGE_COUNTY_OH,0.0034476183211929368 +3914,LAKE_COUNTY_OH,0.29569556208128345 +3914,TRUMBULL_COUNTY_OH,0.25676239146739893 +3914,PORTAGE_COUNTY_OH,0.2022284937009776 +3914,ASHTABULA_COUNTY_OH,0.12404052731271373 +3914,GEAUGA_COUNTY_OH,0.12127302543762633 +3915,FRANKLIN_COUNTY_OH,0.6828839479806262 +3915,MIAMI_COUNTY_OH,0.11660882498760536 +3915,CLARK_COUNTY_OH,0.06946849217548276 +3915,MADISON_COUNTY_OH,0.05571107127874604 +3915,SHELBY_COUNTY_OH,0.04060740119242846 +3915,FAYETTE_COUNTY_OH,0.034720262385111174 +4001,TULSA_COUNTY_OK,0.845186905442932 +4001,WAGONER_COUNTY_OK,0.08189591486492118 +4001,CREEK_COUNTY_OK,0.05091485860702059 +4001,ROGERS_COUNTY_OK,0.022002321085126238 +4002,ROGERS_COUNTY_OK,0.09826979394371053 +4002,MUSKOGEE_COUNTY_OK,0.08377500880825287 +4002,WASHINGTON_COUNTY_OK,0.06624184999829517 +4002,LE_FLORE_COUNTY_OK,0.06077883897756074 +4002,CHEROKEE_COUNTY_OK,0.05945160259688762 +4002,BRYAN_COUNTY_OK,0.05817487949426106 +4002,PITTSBURG_COUNTY_OK,0.05527794299829139 +4002,DELAWARE_COUNTY_OK,0.051014622331162524 +4002,SEQUOYAH_COUNTY_OK,0.049605301873663764 +4002,MAYES_COUNTY_OK,0.04930853636514028 +4002,OKMULGEE_COUNTY_OK,0.04635350959941708 +4002,MCCURTAIN_COUNTY_OK,0.038912903743160185 +4002,OTTAWA_COUNTY_OK,0.03824486564099456 +4002,ADAIR_COUNTY_OK,0.024618908887937555 +4002,MCINTOSH_COUNTY_OK,0.02391929998699283 +4002,WAGONER_COUNTY_OK,0.020369479372271494 +4002,MARSHALL_COUNTY_OK,0.019336482836219537 +4002,CHOCTAW_COUNTY_OK,0.017937265034330088 +4002,ATOKA_COUNTY_OK,0.017860232285309096 +4002,CRAIG_COUNTY_OK,0.0178147703350672 +4002,HUGHES_COUNTY_OK,0.016880274691206017 +4002,HASKELL_COUNTY_OK,0.014599600187404261 +4002,OKFUSKEE_COUNTY_OK,0.014282629367662157 +4002,PUSHMATAHA_COUNTY_OK,0.013653739055982604 +4002,JOHNSTON_COUNTY_OK,0.012971809802354172 +4002,LATIMER_COUNTY_OK,0.011926184946790576 +4002,NOWATA_COUNTY_OK,0.01176959378484627 +4002,COAL_COUNTY_OK,0.006650073054828375 +4003,OKLAHOMA_COUNTY_OK,0.2282720291562641 +4003,PAYNE_COUNTY_OK,0.10310517748471658 +4003,CANADIAN_COUNTY_OK,0.08855356491145654 +4003,GARFIELD_COUNTY_OK,0.07936393680283783 +4003,OSAGE_COUNTY_OK,0.057860434338421286 +4003,KAY_COUNTY_OK,0.05518575626585644 +4003,CREEK_COUNTY_OK,0.039698385216784045 +4003,CUSTER_COUNTY_OK,0.03600712742353237 +4003,CADDO_COUNTY_OK,0.03402700692410759 +4003,JACKSON_COUNTY_OK,0.03129928990959386 +4003,BECKHAM_COUNTY_OK,0.028300064025579923 +4003,TEXAS_COUNTY_OK,0.027004398443685903 +4003,WOODWARD_COUNTY_OK,0.025850170040322223 +4003,PAWNEE_COUNTY_OK,0.019640825336450004 +4003,KINGFISHER_COUNTY_OK,0.019174840346470573 +4003,NOBLE_COUNTY_OK,0.013795176234512945 +4003,WASHITA_COUNTY_OK,0.013795176234512945 +4003,BLAINE_COUNTY_OK,0.011030837093415468 +4003,WOODS_COUNTY_OK,0.01089066274683629 +4003,KIOWA_COUNTY_OK,0.010745437072452457 +4003,MAJOR_COUNTY_OK,0.009827358243956402 +4003,ALFALFA_COUNTY_OK,0.007196879289682284 +4003,GREER_COUNTY_OK,0.006934210243840221 +4003,BEAVER_COUNTY_OK,0.006376038521425838 +4003,LOGAN_COUNTY_OK,0.0058835340604719705 +4003,DEWEY_COUNTY_OK,0.005662538469018312 +4003,GRANT_COUNTY_OK,0.005264746404401727 +4003,ELLIS_COUNTY_OK,0.004734356984912947 +4003,ROGER_MILLS_COUNTY_OK,0.0043466675759056715 +4003,HARPER_COUNTY_OK,0.004131986144207832 +4003,HARMON_COUNTY_OK,0.003141925894495442 +4003,CIMARRON_COUNTY_OK,0.0028994621598719995 +4004,CLEVELAND_COUNTY_OK,0.3732026721557831 +4004,COMANCHE_COUNTY_OK,0.15296071324838673 +4004,OKLAHOMA_COUNTY_OK,0.10393877782969427 +4004,GRADY_COUNTY_OK,0.06919696414815563 +4004,CARTER_COUNTY_OK,0.06061979870433278 +4004,STEPHENS_COUNTY_OK,0.05410989177516511 +4004,MCCLAIN_COUNTY_OK,0.052612171189715486 +4004,PONTOTOC_COUNTY_OK,0.04806975892507608 +4004,GARVIN_COUNTY_OK,0.03239925745387501 +4004,MURRAY_COUNTY_OK,0.017558437622337 +4004,LOVE_COUNTY_OK,0.01281270915680604 +4004,TILLMAN_COUNTY_OK,0.008799424147903065 +4004,COTTON_COUNTY_OK,0.006979681008246303 +4004,JEFFERSON_COUNTY_OK,0.0067397426345233435 +4005,OKLAHOMA_COUNTY_OK,0.673373154684481 +4005,CANADIAN_COUNTY_OK,0.10643413691641304 +4005,POTTAWATOMIE_COUNTY_OK,0.09149734173538586 +4005,LOGAN_COUNTY_OK,0.05669617487718944 +4005,LINCOLN_COUNTY_OK,0.04225188477906727 +4005,SEMINOLE_COUNTY_OK,0.029747307007463347 +4101,WASHINGTON_COUNTY_OR,0.5979122327809473 +4101,MULTNOMAH_COUNTY_OR,0.2306781703433403 +4101,COLUMBIA_COUNTY_OR,0.07446662390312217 +4101,CLATSOP_COUNTY_OR,0.05815842052423574 +4101,TILLAMOOK_COUNTY_OR,0.03878455244835453 +4102,JACKSON_COUNTY_OR,0.3161372908020147 +4102,JOSEPHINE_COUNTY_OR,0.12473644487679993 +4102,UMATILLA_COUNTY_OR,0.11338711344658593 +4102,KLAMATH_COUNTY_OR,0.09828959982101616 +4102,MALHEUR_COUNTY_OR,0.04470489614264332 +4102,WASCO_COUNTY_OR,0.037765024235035236 +4102,UNION_COUNTY_OR,0.037093834827933374 +4102,CROOK_COUNTY_OR,0.03502929019596182 +4102,JEFFERSON_COUNTY_OR,0.03466679127567052 +4102,DESCHUTES_COUNTY_OR,0.03439633309685943 +4102,DOUGLAS_COUNTY_OR,0.032532862084736956 +4102,BAKER_COUNTY_OR,0.023602078138341483 +4102,MORROW_COUNTY_OR,0.017255515010428924 +4102,LAKE_COUNTY_OR,0.011554653084285247 +4102,HARNEY_COUNTY_OR,0.010613005498372295 +4102,WALLOWA_COUNTY_OR,0.010465740312003953 +4102,GRANT_COUNTY_OR,0.010242010509636666 +4102,GILLIAM_COUNTY_OR,0.0028249427577388565 +4102,SHERMAN_COUNTY_OR,0.002647941331815369 +4102,WHEELER_COUNTY_OR,0.0020546325521198397 +4102,CLACKAMAS_COUNTY_OR,0.0 +4102,CURRY_COUNTY_OR,0.0 +4102,MARION_COUNTY_OR,0.0 +4103,MULTNOMAH_COUNTY_OR,0.850297857999544 +4103,CLACKAMAS_COUNTY_OR,0.11575043648551633 +4103,HOOD_RIVER_COUNTY_OR,0.03395170551493963 +4104,LANE_COUNTY_OR,0.5422920725905116 +4104,BENTON_COUNTY_OR,0.13478182065340522 +4104,DOUGLAS_COUNTY_OR,0.12492919932937604 +4104,COOS_COUNTY_OR,0.09194033485885178 +4104,LINCOLN_COUNTY_OR,0.07135999592188137 +4104,CURRY_COUNTY_OR,0.03319985046898364 +4104,LINN_COUNTY_OR,0.0014967261769903485 +4104,POLK_COUNTY_OR,0.0 +4105,CLACKAMAS_COUNTY_OR,0.42576630997339315 +4105,DESCHUTES_COUNTY_OR,0.2463321764520135 +4105,LINN_COUNTY_OR,0.18061650304654855 +4105,MULTNOMAH_COUNTY_OR,0.07367932156061449 +4105,MARION_COUNTY_OR,0.07357736873928257 +4105,JEFFERSON_COUNTY_OR,2.832022814775796e-05 +4105,BENTON_COUNTY_OR,0.0 +4106,MARION_COUNTY_OR,0.4162475290705907 +4106,WASHINGTON_COUNTY_OR,0.2522202964548889 +4106,YAMHILL_COUNTY_OR,0.1525349328530243 +4106,POLK_COUNTY_OR,0.12380559945172272 +4106,CLACKAMAS_COUNTY_OR,0.05519164216977338 +4106,LINN_COUNTY_OR,0.0 +4201,BUCKS_COUNTY_PA,0.8452957772995531 +4201,MONTGOMERY_COUNTY_PA,0.15470422270044687 +4202,PHILADELPHIA_COUNTY_PA,1.0 +4203,PHILADELPHIA_COUNTY_PA,1.0 +4204,MONTGOMERY_COUNTY_PA,0.8161740960823152 +4204,BERKS_COUNTY_PA,0.18382590391768483 +4205,DELAWARE_COUNTY_PA,0.7541582447121457 +4205,MONTGOMERY_COUNTY_PA,0.14899603329210606 +4205,PHILADELPHIA_COUNTY_PA,0.0968378774844221 +4205,CHESTER_COUNTY_PA,7.844511326166936e-06 +4206,CHESTER_COUNTY_PA,0.6986954543552841 +4206,BERKS_COUNTY_PA,0.3013045456447159 +4207,LEHIGH_COUNTY_PA,0.48970341171317816 +4207,NORTHAMPTON_COUNTY_PA,0.4091584789472652 +4207,CARBON_COUNTY_PA,0.08465415465474299 +4207,MONROE_COUNTY_PA,0.01648395468481366 +4208,LUZERNE_COUNTY_PA,0.3707329126932038 +4208,LACKAWANNA_COUNTY_PA,0.28226643621235614 +4208,MONROE_COUNTY_PA,0.20358990986656486 +4208,PIKE_COUNTY_PA,0.07652974507953027 +4208,WAYNE_COUNTY_PA,0.06688099614834495 +4209,LEBANON_COUNTY_PA,0.1872973495941762 +4209,SCHUYLKILL_COUNTY_PA,0.18702540582378044 +4209,NORTHUMBERLAND_COUNTY_PA,0.11982130156472262 +4209,LYCOMING_COUNTY_PA,0.09663809513848214 +4209,COLUMBIA_COUNTY_PA,0.08462550205003766 +4209,BRADFORD_COUNTY_PA,0.07840217345828801 +4209,BERKS_COUNTY_PA,0.07555591582294369 +4209,LUZERNE_COUNTY_PA,0.054954867793490084 +4209,SUSQUEHANNA_COUNTY_PA,0.050249456112459207 +4209,WYOMING_COUNTY_PA,0.034083183415613755 +4209,MONTOUR_COUNTY_PA,0.023711404903355368 +4209,SULLIVAN_COUNTY_PA,0.007635344322650824 +4210,DAUPHIN_COUNTY_PA,0.37444696050539705 +4210,YORK_COUNTY_PA,0.31974050288678774 +4210,CUMBERLAND_COUNTY_PA,0.30581253660781527 +4211,LANCASTER_COUNTY_PA,0.7229834323487574 +4211,YORK_COUNTY_PA,0.27701656765124255 +4212,ALLEGHENY_COUNTY_PA,0.8549611434189608 +4212,WESTMORELAND_COUNTY_PA,0.14503885658103924 +4213,FRANKLIN_COUNTY_PA,0.20386892310266924 +4213,CAMBRIA_COUNTY_PA,0.17450422558781692 +4213,BLAIR_COUNTY_PA,0.16058018157476361 +4213,ADAMS_COUNTY_PA,0.135778386745879 +4213,BEDFORD_COUNTY_PA,0.06220321521211614 +4213,MIFFLIN_COUNTY_PA,0.06032837210275291 +4213,PERRY_COUNTY_PA,0.05993483808886286 +4213,HUNTINGDON_COUNTY_PA,0.05764684963601372 +4213,CUMBERLAND_COUNTY_PA,0.03342293531922015 +4213,JUNIATA_COUNTY_PA,0.03073618316458874 +4213,FULTON_COUNTY_PA,0.01903083423981257 +4213,SOMERSET_COUNTY_PA,0.001965055225504142 +4214,WESTMORELAND_COUNTY_PA,0.31865450941733586 +4214,WASHINGTON_COUNTY_PA,0.27370676693695367 +4214,FAYETTE_COUNTY_PA,0.16840073947593434 +4214,INDIANA_COUNTY_PA,0.09727847753724182 +4214,SOMERSET_COUNTY_PA,0.09495257992903332 +4214,GREENE_COUNTY_PA,0.047006926703501005 +4215,CENTRE_COUNTY_PA,0.20679754832231612 +4215,CLEARFIELD_COUNTY_PA,0.10532852899338968 +4215,ARMSTRONG_COUNTY_PA,0.08571196970964773 +4215,JEFFERSON_COUNTY_PA,0.05816981842523638 +4215,UNION_COUNTY_PA,0.05580207723203079 +4215,TIOGA_COUNTY_PA,0.05366313488411012 +4215,MCKEAN_COUNTY_PA,0.05286168521462639 +4215,LYCOMING_COUNTY_PA,0.052653805120910384 +4215,SNYDER_COUNTY_PA,0.05195171952137896 +4215,WARREN_COUNTY_PA,0.05044949167433688 +4215,CLINTON_COUNTY_PA,0.04896295289097147 +4215,CLARION_COUNTY_PA,0.04868970169860263 +4215,ELK_COUNTY_PA,0.04051700694502552 +4215,VENANGO_COUNTY_PA,0.040383649903773744 +4215,POTTER_COUNTY_PA,0.0214364906702368 +4215,INDIANA_COUNTY_PA,0.011558917663793826 +4215,FOREST_COUNTY_PA,0.009116653418124007 +4215,CAMERON_COUNTY_PA,0.005944847711488579 +4216,ERIE_COUNTY_PA,0.35414877135180717 +4216,BUTLER_COUNTY_PA,0.25332967255659494 +4216,MERCER_COUNTY_PA,0.14466866701967013 +4216,LAWRENCE_COUNTY_PA,0.11252966209723285 +4216,CRAWFORD_COUNTY_PA,0.10974224209501023 +4216,VENANGO_COUNTY_PA,0.02558098487968465 +4217,ALLEGHENY_COUNTY_PA,0.780072012802276 +4217,BEAVER_COUNTY_PA,0.21992798719772405 +4401,PROVIDENCE_COUNTY_RI,0.7510646294870438 +4401,NEWPORT_COUNTY_RI,0.15626060524231952 +4401,BRISTOL_COUNTY_RI,0.09267476527063666 +4402,PROVIDENCE_COUNTY_RI,0.45348360916874353 +4402,KENT_COUNTY_RI,0.3101450752865915 +4402,WASHINGTON_COUNTY_RI,0.23637131554466495 +4501,BERKELEY_COUNTY_SC,0.31436003408082297 +4501,BEAUFORT_COUNTY_SC,0.25590294350542875 +4501,CHARLESTON_COUNTY_SC,0.24581819275905598 +4501,DORCHESTER_COUNTY_SC,0.17442898894014386 +4501,JASPER_COUNTY_SC,0.006265018059280391 +4501,COLLETON_COUNTY_SC,0.0032248226552680993 +4502,LEXINGTON_COUNTY_SC,0.4020648164736742 +4502,RICHLAND_COUNTY_SC,0.30555263039128666 +4502,AIKEN_COUNTY_SC,0.2308633854073356 +4502,ORANGEBURG_COUNTY_SC,0.03336146049729008 +4502,BARNWELL_COUNTY_SC,0.028157707230413442 +4503,ANDERSON_COUNTY_SC,0.2786062439483373 +4503,PICKENS_COUNTY_SC,0.179709082554253 +4503,OCONEE_COUNTY_SC,0.1075035147510134 +4503,GREENWOOD_COUNTY_SC,0.09484494067319106 +4503,LAURENS_COUNTY_SC,0.09236683606763639 +4503,GREENVILLE_COUNTY_SC,0.08824623497683273 +4503,NEWBERRY_COUNTY_SC,0.05158478345304457 +4503,EDGEFIELD_COUNTY_SC,0.035088703015847836 +4503,ABBEVILLE_COUNTY_SC,0.0332260217394872 +4503,SALUDA_COUNTY_SC,0.025795810745017807 +4503,MCCORMICK_COUNTY_SC,0.013027828075338756 +4504,GREENVILLE_COUNTY_SC,0.630477951433526 +4504,SPARTANBURG_COUNTY_SC,0.3695220485664739 +4505,YORK_COUNTY_SC,0.38578837096077156 +4505,LANCASTER_COUNTY_SC,0.13131219194643357 +4505,SUMTER_COUNTY_SC,0.10484215075409872 +4505,KERSHAW_COUNTY_SC,0.089445626665062 +4505,SPARTANBURG_COUNTY_SC,0.07904907522387733 +4505,CHEROKEE_COUNTY_SC,0.0768814174977161 +4505,CHESTER_COUNTY_SC,0.04416551331776084 +4505,UNION_COUNTY_SC,0.03725909595680549 +4505,FAIRFIELD_COUNTY_SC,0.028648639777681742 +4505,LEE_COUNTY_SC,0.02260791789979267 +4506,CHARLESTON_COUNTY_SC,0.31248734963156655 +4506,RICHLAND_COUNTY_SC,0.26357350342722413 +4506,ORANGEBURG_COUNTY_SC,0.08182258302744515 +4506,COLLETON_COUNTY_SC,0.04957029775548274 +4506,DORCHESTER_COUNTY_SC,0.046494548716910736 +4506,CLARENDON_COUNTY_SC,0.042592764809820514 +4506,WILLIAMSBURG_COUNTY_SC,0.04243138713683185 +4506,SUMTER_COUNTY_SC,0.03951701577124852 +4506,JASPER_COUNTY_SC,0.033109775110639436 +4506,HAMPTON_COUNTY_SC,0.025384160918156904 +4506,CALHOUN_COUNTY_SC,0.019309248855312607 +4506,BAMBERG_COUNTY_SC,0.01820422207755975 +4506,FLORENCE_COUNTY_SC,0.014508946887599083 +4506,ALLENDALE_COUNTY_SC,0.010994195874202 +4507,HORRY_COUNTY_SC,0.48007051393388706 +4507,FLORENCE_COUNTY_SC,0.17293419200960608 +4507,GEORGETOWN_COUNTY_SC,0.08671189806387555 +4507,DARLINGTON_COUNTY_SC,0.08602946103886336 +4507,CHESTERFIELD_COUNTY_SC,0.0591805558784633 +4507,MARION_COUNTY_SC,0.039910941284431276 +4507,DILLON_COUNTY_SC,0.03869240142614295 +4507,MARLBORO_COUNTY_SC,0.03647003636473045 +4600,MINNEHAHA_COUNTY_SD,0.2224217208940899 +4600,PENNINGTON_COUNTY_SD,0.12318266045764645 +4600,LINCOLN_COUNTY_SD,0.07348982199630752 +4600,BROWN_COUNTY_SD,0.04319660030202996 +4600,BROOKINGS_COUNTY_SD,0.03876878241775097 +4600,MEADE_COUNTY_SD,0.033667656515918604 +4600,CODINGTON_COUNTY_SD,0.0319454767122268 +4600,LAWRENCE_COUNTY_SD,0.029061643209908568 +4600,YANKTON_COUNTY_SD,0.02628946380095346 +4600,DAVISON_COUNTY_SD,0.022506758456105842 +4600,BEADLE_COUNTY_SD,0.021596608422327662 +4600,HUGHES_COUNTY_SD,0.020035706753493702 +4600,UNION_COUNTY_SD,0.018959767308358154 +4600,CLAY_COUNTY_SD,0.016880068842079383 +4600,OGLALA_LAKOTA_COUNTY_SD,0.015419543075359745 +4600,LAKE_COUNTY_SD,0.012472551702048232 +4600,ROBERTS_COUNTY_SD,0.01159398060376669 +4600,BUTTE_COUNTY_SD,0.01155225129614613 +4600,CHARLES_MIX_COUNTY_SD,0.010571048657500505 +4600,TODD_COUNTY_SD,0.010510146424756983 +4600,TURNER_COUNTY_SD,0.009781575270084485 +4600,CUSTER_COUNTY_SD,0.009381199480752075 +4600,GRANT_COUNTY_SD,0.008521801307593493 +4600,HUTCHINSON_COUNTY_SD,0.00837631264048397 +4600,BON_HOMME_COUNTY_SD,0.007898117331534837 +4600,FALL_RIVER_COUNTY_SD,0.007864282757788436 +4600,SPINK_COUNTY_SD,0.0071740574533618595 +4600,MOODY_COUNTY_SD,0.007145861975239859 +4600,HAMLIN_COUNTY_SD,0.006951877085760494 +4600,MCCOOK_COUNTY_SD,0.006408268267568321 +4600,TRIPP_COUNTY_SD,0.006342854758325279 +4600,DAY_COUNTY_SD,0.006145486411471274 +4600,WALWORTH_COUNTY_SD,0.00599435864873735 +4600,BRULE_COUNTY_SD,0.005917666948245508 +4600,DEWEY_COUNTY_SD,0.005908644395246468 +4600,KINGSBURY_COUNTY_SD,0.005849997800752706 +4600,MARSHALL_COUNTY_SD,0.004856389151733402 +4600,DEUEL_COUNTY_SD,0.004843983141359721 +4600,GREGORY_COUNTY_SD,0.004504509584770833 +4600,EDMUNDS_COUNTY_SD,0.004495487031771793 +4600,CORSON_COUNTY_SD,0.0044007502252818706 +4600,CLARK_COUNTY_SD,0.004327441982164669 +4600,LYMAN_COUNTY_SD,0.004193231506303945 +4600,HANSON_COUNTY_SD,0.003903381991209778 +4600,BENNETT_COUNTY_SD,0.0038131564612193755 +4600,HAND_COUNTY_SD,0.003546991147747689 +4600,STANLEY_COUNTY_SD,0.003360900992142484 +4600,DOUGLAS_COUNTY_SD,0.00319736721903488 +4600,PERKINS_COUNTY_SD,0.00319736721903488 +4600,JACKSON_COUNTY_SD,0.003164660464413359 +4600,AURORA_COUNTY_SD,0.0030981191360454374 +4600,POTTER_COUNTY_SD,0.00278796887670343 +4600,ZIEBACH_COUNTY_SD,0.0027214275483355083 +4600,MCPHERSON_COUNTY_SD,0.002719171910085748 +4600,SANBORN_COUNTY_SD,0.002627818560970466 +4600,MINER_COUNTY_SD,0.002591728348974305 +4600,FAULK_COUNTY_SD,0.00239661564037006 +4600,BUFFALO_COUNTY_SD,0.002196991655266295 +4600,MELLETTE_COUNTY_SD,0.0021631570815198943 +4600,HAAKON_COUNTY_SD,0.002111277401775413 +4600,JERAULD_COUNTY_SD,0.0018755632046754869 +4600,SULLY_COUNTY_SD,0.0016308264545765209 +4600,CAMPBELL_COUNTY_SD,0.0015530069349597988 +4600,HARDING_COUNTY_SD,0.0014785708727177171 +4600,HYDE_COUNTY_SD,0.0014233077355985957 +4600,JONES_COUNTY_SD,0.001034210137514986 +4701,SULLIVAN_COUNTY_TN,0.20597600378188524 +4701,WASHINGTON_COUNTY_TN,0.1732074788603815 +4701,SEVIER_COUNTY_TN,0.12812047856996814 +4701,GREENE_COUNTY_TN,0.09135909547306774 +4701,HAMBLEN_COUNTY_TN,0.08399718181830021 +4701,HAWKINS_COUNTY_TN,0.073867876244838 +4701,CARTER_COUNTY_TN,0.0733925359858622 +4701,JEFFERSON_COUNTY_TN,0.06780045085697989 +4701,COCKE_COUNTY_TN,0.0468815725558069 +4701,JOHNSON_COUNTY_TN,0.023373717720815083 +4701,UNICOI_COUNTY_TN,0.02334767167922737 +4701,HANCOCK_COUNTY_TN,0.008675936452867735 +4702,KNOX_COUNTY_TN,0.6237649292654626 +4702,BLOUNT_COUNTY_TN,0.17617542529930158 +4702,LOUDON_COUNTY_TN,0.07147815192916518 +4702,CLAIBORNE_COUNTY_TN,0.04172966552975695 +4702,GRAINGER_COUNTY_TN,0.030639261021708074 +4702,CAMPBELL_COUNTY_TN,0.02701104742853943 +4702,UNION_COUNTY_TN,0.025788185775996227 +4702,JEFFERSON_COUNTY_TN,0.003413333750069999 +4703,HAMILTON_COUNTY_TN,0.4769121375856101 +4703,BRADLEY_COUNTY_TN,0.14145605186287802 +4703,ANDERSON_COUNTY_TN,0.10043744326846567 +4703,ROANE_COUNTY_TN,0.06954814024751553 +4703,MCMINN_COUNTY_TN,0.06938144558135416 +4703,MONROE_COUNTY_TN,0.06023147117159002 +4703,MORGAN_COUNTY_TN,0.027393924239878832 +4703,CAMPBELL_COUNTY_TN,0.024132959833096965 +4703,POLK_COUNTY_TN,0.02284758768074325 +4703,SCOTT_COUNTY_TN,0.007658838528867479 +4704,RUTHERFORD_COUNTY_TN,0.44471734872478746 +4704,COFFEE_COUNTY_TN,0.07538886689448242 +4704,BEDFORD_COUNTY_TN,0.06542366436072679 +4704,LAWRENCE_COUNTY_TN,0.057508282630438404 +4704,FRANKLIN_COUNTY_TN,0.05570459659943324 +4704,WARREN_COUNTY_TN,0.05270149191531922 +4704,LINCOLN_COUNTY_TN,0.04599594724120687 +4704,RHEA_COUNTY_TN,0.04280661360226704 +4704,GILES_COUNTY_TN,0.03951960743457243 +4704,MARION_COUNTY_TN,0.037554436156026 +4704,SEQUATCHIE_COUNTY_TN,0.02061020586764461 +4704,BLEDSOE_COUNTY_TN,0.019421205617602934 +4704,GRUNDY_COUNTY_TN,0.017618821886981162 +4704,MEIGS_COUNTY_TN,0.016614748291381896 +4704,MOORE_COUNTY_TN,0.008414162777129522 +4705,DAVIDSON_COUNTY_TN,0.4511916715177419 +4705,WILLIAMSON_COUNTY_TN,0.20997407116559944 +4705,WILSON_COUNTY_TN,0.1462576396295732 +4705,MAURY_COUNTY_TN,0.13149865016389473 +4705,MARSHALL_COUNTY_TN,0.04469240276035949 +4705,LEWIS_COUNTY_TN,0.016385564762831258 +4706,SUMNER_COUNTY_TN,0.2556171544439105 +4706,DAVIDSON_COUNTY_TN,0.24570272871354693 +4706,PUTNAM_COUNTY_TN,0.1039940302472681 +4706,CUMBERLAND_COUNTY_TN,0.07962926064404047 +4706,WILSON_COUNTY_TN,0.04614056267263642 +4706,WHITE_COUNTY_TN,0.035619264173279104 +4706,MACON_COUNTY_TN,0.032838849233790574 +4706,OVERTON_COUNTY_TN,0.02931612210905217 +4706,DEKALB_COUNTY_TN,0.026150225754065462 +4706,SMITH_COUNTY_TN,0.025921020588093573 +4706,FENTRESS_COUNTY_TN,0.024078263145762765 +4706,SCOTT_COUNTY_TN,0.020796461905710724 +4706,CANNON_COUNTY_TN,0.018891193963569402 +4706,JACKSON_COUNTY_TN,0.015128843256224028 +4706,TROUSDALE_COUNTY_TN,0.015126238652065256 +4706,CLAY_COUNTY_TN,0.00987275206382322 +4706,VAN_BUREN_COUNTY_TN,0.008032599225651183 +4706,PICKETT_COUNTY_TN,0.006512812699008036 +4706,WARREN_COUNTY_TN,0.0006316165085020791 +4707,MONTGOMERY_COUNTY_TN,0.28659631630833826 +4707,DAVIDSON_COUNTY_TN,0.2354028215676852 +4707,WILLIAMSON_COUNTY_TN,0.11264001375230996 +4707,ROBERTSON_COUNTY_TN,0.09481149828551931 +4707,DICKSON_COUNTY_TN,0.07073453744183593 +4707,CHEATHAM_COUNTY_TN,0.05348815100453071 +4707,HICKMAN_COUNTY_TN,0.03245987932868932 +4707,HUMPHREYS_COUNTY_TN,0.024730716487535016 +4707,WAYNE_COUNTY_TN,0.02113896735258917 +4707,STEWART_COUNTY_TN,0.017785539498170917 +4707,DECATUR_COUNTY_TN,0.014891824277775825 +4707,BENTON_COUNTY_TN,0.013637707375327365 +4707,PERRY_COUNTY_TN,0.010895059196141019 +4707,HOUSTON_COUNTY_TN,0.010786968123552003 +4708,SHELBY_COUNTY_TN,0.2506501743131333 +4708,MADISON_COUNTY_TN,0.128697398391136 +4708,GIBSON_COUNTY_TN,0.06567379156134298 +4708,FAYETTE_COUNTY_TN,0.05468366431340681 +4708,DYER_COUNTY_TN,0.047926018823474255 +4708,WEAKLEY_COUNTY_TN,0.042848343015949295 +4708,HENRY_COUNTY_TN,0.041932824654141124 +4708,OBION_COUNTY_TN,0.04009397411804847 +4708,TIPTON_COUNTY_TN,0.03955872796342094 +4708,CARROLL_COUNTY_TN,0.03703747113773016 +4708,HENDERSON_COUNTY_TN,0.0362586944942575 +4708,HARDIN_COUNTY_TN,0.03494206709199853 +4708,MCNAIRY_COUNTY_TN,0.033685345585391296 +4708,HARDEMAN_COUNTY_TN,0.03315921554531946 +4708,LAUDERDALE_COUNTY_TN,0.03274378118199541 +4708,HAYWOOD_COUNTY_TN,0.023264324346146684 +4708,CHESTER_COUNTY_TN,0.022583220358627948 +4708,CROCKETT_COUNTY_TN,0.01811632422633489 +4708,LAKE_COUNTY_TN,0.00912262606609704 +4708,BENTON_COUNTY_TN,0.007022012812047857 +4709,SHELBY_COUNTY_TN,0.960157370183273 +4709,TIPTON_COUNTY_TN,0.03984262981672703 +4801,SMITH_COUNTY_TX,0.30441063538234675 +4801,GREGG_COUNTY_TX,0.16198318876330367 +4801,BOWIE_COUNTY_TX,0.10985192708611749 +4801,HARRISON_COUNTY_TX,0.0897524990645213 +4801,RUSK_COUNTY_TX,0.06807677313957082 +4801,TITUS_COUNTY_TX,0.04073993431440168 +4801,CASS_COUNTY_TX,0.03709841235901 +4801,UPSHUR_COUNTY_TX,0.034910630819036045 +4801,SHELBY_COUNTY_TX,0.03131995718310741 +4801,PANOLA_COUNTY_TX,0.02932383469341723 +4801,CAMP_COUNTY_TX,0.01625060137916288 +4801,MORRIS_COUNTY_TX,0.015610434075153817 +4801,FRANKLIN_COUNTY_TX,0.013506095931221781 +4801,SABINE_COUNTY_TX,0.012899827506854745 +4801,MARION_COUNTY_TX,0.012679484789181563 +4801,RED_RIVER_COUNTY_TX,0.011262250859532169 +4801,SAN_AUGUSTINE_COUNTY_TX,0.01032351265406063 +4802,HARRIS_COUNTY_TX,0.5580133691966096 +4802,MONTGOMERY_COUNTY_TX,0.4419866308033904 +4803,COLLIN_COUNTY_TX,0.8874478967700886 +4803,HUNT_COUNTY_TX,0.11255210322991133 +4804,COLLIN_COUNTY_TX,0.4006378204584954 +4804,GRAYSON_COUNTY_TX,0.17672137858920686 +4804,ROCKWALL_COUNTY_TX,0.14057474246629995 +4804,DENTON_COUNTY_TX,0.06674559021208964 +4804,LAMAR_COUNTY_TX,0.06530488782730347 +4804,HOPKINS_COUNTY_TX,0.047963003284279916 +4804,FANNIN_COUNTY_TX,0.04649622483823063 +4804,HUNT_COUNTY_TX,0.017770835750801513 +4804,RAINS_COUNTY_TX,0.015859460460216405 +4804,BOWIE_COUNTY_TX,0.011262250859532169 +4804,DELTA_COUNTY_TX,0.006818890020300214 +4804,RED_RIVER_COUNTY_TX,0.003844915233243849 +4805,DALLAS_COUNTY_TX,0.5489362922709251 +4805,KAUFMAN_COUNTY_TX,0.18945562310704092 +4805,HENDERSON_COUNTY_TX,0.1071074216381764 +4805,VAN_ZANDT_COUNTY_TX,0.07762973818330689 +4805,WOOD_COUNTY_TX,0.05846644076105592 +4805,UPSHUR_COUNTY_TX,0.018404484039494802 +4806,ELLIS_COUNTY_TX,0.25092341851947947 +4806,DALLAS_COUNTY_TX,0.21594238233503305 +4806,TARRANT_COUNTY_TX,0.1977100002998747 +4806,ANDERSON_COUNTY_TX,0.07551888102405908 +4806,NAVARRO_COUNTY_TX,0.06861133239546433 +4806,CHEROKEE_COUNTY_TX,0.06572732001976565 +4806,JOHNSON_COUNTY_TX,0.06248215419557307 +4806,HILL_COUNTY_TX,0.046772631087619476 +4806,FREESTONE_COUNTY_TX,0.016311880123131162 +4807,HARRIS_COUNTY_TX,0.7392719824455956 +4807,FORT_BEND_COUNTY_TX,0.26072801755440445 +4808,HARRIS_COUNTY_TX,0.5102250755227924 +4808,MONTGOMERY_COUNTY_TX,0.3669488531096355 +4808,POLK_COUNTY_TX,0.06535052093451388 +4808,SAN_JACINTO_COUNTY_TX,0.03572681153657102 +4808,WALKER_COUNTY_TX,0.021748738896487162 +4809,HARRIS_COUNTY_TX,0.6859881588605804 +4809,FORT_BEND_COUNTY_TX,0.19760830366094861 +4809,BRAZORIA_COUNTY_TX,0.11640353747847096 +4810,BRAZOS_COUNTY_TX,0.3048930425157141 +4810,TRAVIS_COUNTY_TX,0.2417720248191951 +4810,WALLER_COUNTY_TX,0.07404819116882033 +4810,BASTROP_COUNTY_TX,0.0689737896470214 +4810,WILLIAMSON_COUNTY_TX,0.06414580690415875 +4810,WASHINGTON_COUNTY_TX,0.046682668676261786 +4810,AUSTIN_COUNTY_TX,0.03933182700619437 +4810,GRIMES_COUNTY_TX,0.0381597080524181 +4810,FAYETTE_COUNTY_TX,0.03185842784819039 +4810,COLORADO_COUNTY_TX,0.02680227956927562 +4810,BURLESON_COUNTY_TX,0.02300169364017904 +4810,LEE_COUNTY_TX,0.02278786993782163 +4810,MADISON_COUNTY_TX,0.017542670214749402 +4811,BELL_COUNTY_TX,0.2290215956187287 +4811,MIDLAND_COUNTY_TX,0.22286977465612343 +4811,ECTOR_COUNTY_TX,0.216560618119027 +4811,TOM_GREEN_COUNTY_TX,0.1573395078805456 +4811,BROWN_COUNTY_TX,0.04994748925201357 +4811,LAMPASAS_COUNTY_TX,0.028355803912673512 +4811,LLANO_COUNTY_TX,0.027852330074301726 +4811,RUNNELS_COUNTY_TX,0.012980184895522624 +4811,COLEMAN_COUNTY_TX,0.010074721286585438 +4811,MCCULLOCH_COUNTY_TX,0.010003920278064406 +4811,SAN_SABA_COUNTY_TX,0.007512773681954003 +4811,MILLS_COUNTY_TX,0.005842394332772607 +4811,MASON_COUNTY_TX,0.0051828960496970635 +4811,CONCHO_COUNTY_TX,0.0043306616878698205 +4811,COKE_COUNTY_TX,0.004307061351696143 +4811,MENARD_COUNTY_TX,0.002572436642930847 +4811,IRION_COUNTY_TX,0.0019837393683763364 +4811,STERLING_COUNTY_TX,0.0017988700683491958 +4811,GLASSCOCK_COUNTY_TX,0.0014632208427680048 +4812,TARRANT_COUNTY_TX,0.8475775990988114 +4812,PARKER_COUNTY_TX,0.15242240090118866 +4813,RANDALL_COUNTY_TX,0.1860357841951596 +4813,DENTON_COUNTY_TX,0.18146924824641053 +4813,WICHITA_COUNTY_TX,0.17096423298717536 +4813,POTTER_COUNTY_TX,0.15665663482647824 +4813,WISE_COUNTY_TX,0.02989990629018849 +4813,MOORE_COUNTY_TX,0.028229254643526026 +4813,GRAY_COUNTY_TX,0.02805610957571528 +4813,HUTCHINSON_COUNTY_TX,0.027249861550031656 +4813,MONTAGUE_COUNTY_TX,0.026388101365202598 +4813,DEAF_SMITH_COUNTY_TX,0.024561486985702975 +4813,WILBARGER_COUNTY_TX,0.01703298083112276 +4813,CLAY_COUNTY_TX,0.013505315289238175 +4813,OCHILTREE_COUNTY_TX,0.013237006520035264 +4813,ARCHER_COUNTY_TX,0.011313906721068582 +4813,DALLAM_COUNTY_TX,0.009404024102850813 +4813,CHILDRESS_COUNTY_TX,0.008807929251074887 +4813,CARSON_COUNTY_TX,0.0076752168608931375 +4813,HARTLEY_COUNTY_TX,0.0071134866790643824 +4813,HANSFORD_COUNTY_TX,0.006985280025799937 +4813,WHEELER_COUNTY_TX,0.0065953731937070355 +4813,KNOX_COUNTY_TX,0.004431720705110159 +4813,DONLEY_COUNTY_TX,0.004306157487995495 +4813,LIPSCOMB_COUNTY_TX,0.004043135591092149 +4813,HALL_COUNTY_TX,0.0037338535615676106 +4813,SHERMAN_COUNTY_TX,0.0036770196843472895 +4813,COLLINGSWORTH_COUNTY_TX,0.003505196334611435 +4813,ARMSTRONG_COUNTY_TX,0.0024425350023989183 +4813,DICKENS_COUNTY_TX,0.0023394409925574055 +4813,OLDHAM_COUNTY_TX,0.002323580375658711 +4813,BRISCOE_COUNTY_TX,0.001896665437468857 +4813,COTTLE_COUNTY_TX,0.0018239709433498417 +4813,FOARD_COUNTY_TX,0.0014472812920058526 +4813,MOTLEY_COUNTY_TX,0.0014049863136093345 +4813,ROBERTS_COUNTY_TX,0.0010930608479350137 +4813,KING_COUNTY_TX,0.00035025528984616524 +4814,GALVESTON_COUNTY_TX,0.45722026579329245 +4814,JEFFERSON_COUNTY_TX,0.2411331613182492 +4814,BRAZORIA_COUNTY_TX,0.19107364270841617 +4814,ORANGE_COUNTY_TX,0.11057293018004216 +4814,CHAMBERS_COUNTY_TX,0.0 +4815,HIDALGO_COUNTY_TX,0.7514664524952835 +4815,GUADALUPE_COUNTY_TX,0.08977596751965809 +4815,WILSON_COUNTY_TX,0.06486811380114657 +4815,JIM_WELLS_COUNTY_TX,0.05070620492915786 +4815,KARNES_COUNTY_TX,0.019178943059008823 +4815,LIVE_OAK_COUNTY_TX,0.01477860772086098 +4815,BROOKS_COUNTY_TX,0.009225710474884189 +4816,EL_PASO_COUNTY_TX,1.0 +4817,MCLENNAN_COUNTY_TX,0.339743698393845 +4817,WILLIAMSON_COUNTY_TX,0.11321443518599403 +4817,ANGELINA_COUNTY_TX,0.11264206564126902 +4817,TRAVIS_COUNTY_TX,0.09109802382569718 +4817,NACOGDOCHES_COUNTY_TX,0.08429477944215483 +4817,WALKER_COUNTY_TX,0.07786181512854846 +4817,MILAM_COUNTY_TX,0.03227434102533681 +4817,LIMESTONE_COUNTY_TX,0.028874022636628784 +4817,HOUSTON_COUNTY_TX,0.02876971839157639 +4817,FALLS_COUNTY_TX,0.022122930375612623 +4817,ROBERTSON_COUNTY_TX,0.021847827929286936 +4817,LEON_COUNTY_TX,0.020494480349732135 +4817,TRINITY_COUNTY_TX,0.017734329265033176 +4817,FREESTONE_COUNTY_TX,0.009027532409284642 +4818,HARRIS_COUNTY_TX,1.0 +4819,LUBBOCK_COUNTY_TX,0.4050120797353801 +4819,TAYLOR_COUNTY_TX,0.1867150290682893 +4819,HOWARD_COUNTY_TX,0.04545057478158039 +4819,HALE_COUNTY_TX,0.042402283219924196 +4819,GAINES_COUNTY_TX,0.02815953855801989 +4819,HOCKLEY_COUNTY_TX,0.028080006571167437 +4819,JONES_COUNTY_TX,0.025636679630815125 +4819,ANDREWS_COUNTY_TX,0.024263775005312998 +4819,SCURRY_COUNTY_TX,0.022075993465339047 +4819,NOLAN_COUNTY_TX,0.01921544954477716 +4819,LAMB_COUNTY_TX,0.017008110958855887 +4819,DAWSON_COUNTY_TX,0.01624017095465764 +4819,TERRY_COUNTY_TX,0.015425294040185817 +4819,PARMER_COUNTY_TX,0.012867232430275872 +4819,MITCHELL_COUNTY_TX,0.0117211895377627 +4819,YOAKUM_COUNTY_TX,0.010031460767913928 +4819,CASTRO_COUNTY_TX,0.00961033237851489 +4819,SWISHER_COUNTY_TX,0.009088811153252923 +4819,BAILEY_COUNTY_TX,0.009001456348021544 +4819,GARZA_COUNTY_TX,0.007582918615308995 +4819,LYNN_COUNTY_TX,0.007296081941414913 +4819,HASKELL_COUNTY_TX,0.007061397390047028 +4819,FLOYD_COUNTY_TX,0.0070431441471628595 +4819,MARTIN_COUNTY_TX,0.006828016641742298 +4819,CROSBY_COUNTY_TX,0.006692421123174187 +4819,FISHER_COUNTY_TX,0.004787564847904853 +4819,SHACKELFORD_COUNTY_TX,0.004048308511096016 +4819,COCHRAN_COUNTY_TX,0.0033207864018555723 +4819,CALLAHAN_COUNTY_TX,0.00202871756626905 +4819,THROCKMORTON_COUNTY_TX,0.0018774764109430798 +4819,STONEWALL_COUNTY_TX,0.0016232348136278711 +4819,KENT_COUNTY_TX,0.0009817637065556522 +4819,BORDEN_COUNTY_TX,0.0008226997328507524 +4820,BEXAR_COUNTY_TX,1.0 +4821,BEXAR_COUNTY_TX,0.4439266897613649 +4821,COMAL_COUNTY_TX,0.1775297364883629 +4821,HAYS_COUNTY_TX,0.1358588867868686 +4821,KERR_COUNTY_TX,0.06857743351582231 +4821,KENDALL_COUNTY_TX,0.05773109583343655 +4821,TRAVIS_COUNTY_TX,0.035921078192981105 +4821,GILLESPIE_COUNTY_TX,0.034844136862815143 +4821,BANDERA_COUNTY_TX,0.027185597669843166 +4821,BLANCO_COUNTY_TX,0.014829456040324021 +4821,REAL_COUNTY_TX,0.00359588884818126 +4822,FORT_BEND_COUNTY_TX,0.614405459284186 +4822,BRAZORIA_COUNTY_TX,0.17757797720169963 +4822,HARRIS_COUNTY_TX,0.10654809012408294 +4822,WHARTON_COUNTY_TX,0.054199093335349884 +4822,MATAGORDA_COUNTY_TX,0.0472693800546815 +4823,BEXAR_COUNTY_TX,0.45323872907614177 +4823,EL_PASO_COUNTY_TX,0.12921783861226135 +4823,MAVERICK_COUNTY_TX,0.07580781611362987 +4823,MEDINA_COUNTY_TX,0.06645870492743602 +4823,VAL_VERDE_COUNTY_TX,0.062317804301193554 +4823,UVALDE_COUNTY_TX,0.03216859044371283 +4823,FRIO_COUNTY_TX,0.024076678688636225 +4823,PECOS_COUNTY_TX,0.019896490580171344 +4823,REEVES_COUNTY_TX,0.019313726260538868 +4823,WARD_COUNTY_TX,0.015248781433259735 +4823,ZAVALA_COUNTY_TX,0.012663665103024874 +4823,BREWSTER_COUNTY_TX,0.012501276843172229 +4823,DIMMIT_COUNTY_TX,0.011282055311536638 +4823,WINKLER_COUNTY_TX,0.010202959133160992 +4823,LA_SALLE_COUNTY_TX,0.008727059384338962 +4823,PRESIDIO_COUNTY_TX,0.008029051783520735 +4823,CRANE_COUNTY_TX,0.006122299312992894 +4823,SUTTON_COUNTY_TX,0.004415913001799367 +4823,UPTON_COUNTY_TX,0.004332099706391549 +4823,HUDSPETH_COUNTY_TX,0.004193283935872353 +4823,KINNEY_COUNTY_TX,0.004097684395797811 +4823,CROCKETT_COUNTY_TX,0.00405708733083465 +4823,SCHLEICHER_COUNTY_TX,0.003209787297571248 +4823,CULBERSON_COUNTY_TX,0.002865367036754749 +4823,JEFF_DAVIS_COUNTY_TX,0.0026139271505312978 +4823,EDWARDS_COUNTY_TX,0.0018622266573424375 +4823,TERRELL_COUNTY_TX,0.0009952828829678287 +4823,LOVING_COUNTY_TX,8.381329540781716e-05 +4824,TARRANT_COUNTY_TX,0.6635692651896317 +4824,DALLAS_COUNTY_TX,0.33643073481036834 +4825,TARRANT_COUNTY_TX,0.4993552693852699 +4825,JOHNSON_COUNTY_TX,0.17210721954870162 +4825,HOOD_COUNTY_TX,0.08031166108421656 +4825,ERATH_COUNTY_TX,0.05547030132192592 +4825,PARKER_COUNTY_TX,0.04082989672575937 +4825,PALO_PINTO_COUNTY_TX,0.03703974122116802 +4825,YOUNG_COUNTY_TX,0.023295049329388893 +4825,EASTLAND_COUNTY_TX,0.023109909294420895 +4825,COMANCHE_COUNTY_TX,0.017723898840527937 +4825,CALLAHAN_COUNTY_TX,0.015843814823458546 +4825,SOMERVELL_COUNTY_TX,0.012001507196341007 +4825,STEPHENS_COUNTY_TX,0.011865911677772895 +4825,JACK_COUNTY_TX,0.011045819551048454 +4826,DENTON_COUNTY_TX,0.8856851550287033 +4826,WISE_COUNTY_TX,0.05998797893575771 +4826,COOKE_COUNTY_TX,0.054326866035539066 +4826,TARRANT_COUNTY_TX,0.0 +4827,NUECES_COUNTY_TX,0.46047455823892713 +4827,VICTORIA_COUNTY_TX,0.11906199192424383 +4827,SAN_PATRICIO_COUNTY_TX,0.08964297960721629 +4827,CALDWELL_COUNTY_TX,0.059822395946737036 +4827,BASTROP_COUNTY_TX,0.057776728940646976 +4827,BEE_COUNTY_TX,0.0404791737017707 +4827,ARANSAS_COUNTY_TX,0.031069626994981663 +4827,LAVACA_COUNTY_TX,0.02651544289538154 +4827,CALHOUN_COUNTY_TX,0.026214264387792754 +4827,DEWITT_COUNTY_TX,0.025846591923983066 +4827,GONZALES_COUNTY_TX,0.025623641600183576 +4827,JACKSON_COUNTY_TX,0.01954140031056589 +4827,GOLIAD_COUNTY_TX,0.009142267078842276 +4827,REFUGIO_COUNTY_TX,0.008788936448727292 +4828,WEBB_COUNTY_TX,0.35047477592964105 +4828,BEXAR_COUNTY_TX,0.33059808515132866 +4828,GUADALUPE_COUNTY_TX,0.13625813325215935 +4828,STARR_COUNTY_TX,0.08649227382047342 +4828,ATASCOSA_COUNTY_TX,0.06426696092233933 +4828,ZAPATA_COUNTY_TX,0.01822347073866134 +4828,DUVAL_COUNTY_TX,0.012899052547467753 +4828,MCMULLEN_COUNTY_TX,0.0007872476379290664 +4829,HARRIS_COUNTY_TX,1.0 +4830,DALLAS_COUNTY_TX,0.9313547687248936 +4830,TARRANT_COUNTY_TX,0.06864523127510636 +4831,WILLIAMSON_COUNTY_TX,0.5376036360459825 +4831,BELL_COUNTY_TX,0.2555088938925953 +4831,CORYELL_COUNTY_TX,0.10833690792673148 +4831,BURNET_COUNTY_TX,0.06405584449280105 +4831,BOSQUE_COUNTY_TX,0.023774848856629904 +4831,HAMILTON_COUNTY_TX,0.010719868785259724 +4832,DALLAS_COUNTY_TX,0.8498775077022166 +4832,COLLIN_COUNTY_TX,0.09976701039261422 +4832,DENTON_COUNTY_TX,0.05035548190516919 +4833,DALLAS_COUNTY_TX,0.5249984680314008 +4833,TARRANT_COUNTY_TX,0.47500153196859923 +4834,CAMERON_COUNTY_TX,0.5489232542402935 +4834,HIDALGO_COUNTY_TX,0.38386048264181793 +4834,KLEBERG_COUNTY_TX,0.040470047080328614 +4834,WILLACY_COUNTY_TX,0.02628988496545574 +4834,KENEDY_COUNTY_TX,0.0004563310721042208 +4835,BEXAR_COUNTY_TX,0.39608494016195844 +4835,TRAVIS_COUNTY_TX,0.39243429158512466 +4835,HAYS_COUNTY_TX,0.17844500623869766 +4835,COMAL_COUNTY_TX,0.033035762014219275 +4836,HARRIS_COUNTY_TX,0.5684346670804068 +4836,LIBERTY_COUNTY_TX,0.11946486707075869 +4836,JEFFERSON_COUNTY_TX,0.09332622326062893 +4836,HARDIN_COUNTY_TX,0.07331415004426411 +4836,CHAMBERS_COUNTY_TX,0.06071941245418762 +4836,JASPER_COUNTY_TX,0.04299942502284915 +4836,TYLER_COUNTY_TX,0.02581269304434104 +4836,NEWTON_COUNTY_TX,0.015928562022563614 +4837,TRAVIS_COUNTY_TX,0.9209256480227175 +4837,WILLIAMSON_COUNTY_TX,0.07907435197728253 +4838,HARRIS_COUNTY_TX,1.0 +4901,WEBER_COUNTY_UT,0.3206036405250494 +4901,DAVIS_COUNTY_UT,0.28647494082435104 +4901,CACHE_COUNTY_UT,0.162799057102056 +4901,SALT_LAKE_COUNTY_UT,0.11002024687493887 +4901,BOX_ELDER_COUNTY_UT,0.07050460689763102 +4901,SUMMIT_COUNTY_UT,0.031496361431170405 +4901,MORGAN_COUNTY_UT,0.015032326532209159 +4901,RICH_COUNTY_UT,0.003068819812594143 +4902,SALT_LAKE_COUNTY_UT,0.3914787065474677 +4902,WASHINGTON_COUNTY_UT,0.22041584342416715 +4902,DAVIS_COUNTY_UT,0.15694995989749408 +4902,TOOELE_COUNTY_UT,0.08888329192668089 +4902,IRON_COUNTY_UT,0.07004367260705413 +4902,SEVIER_COUNTY_UT,0.026313601596275358 +4902,MILLARD_COUNTY_UT,0.01586371994757331 +4902,KANE_COUNTY_UT,0.009373960758230795 +4902,BEAVER_COUNTY_UT,0.008646491519787164 +4902,GARFIELD_COUNTY_UT,0.006214665779847023 +4902,WAYNE_COUNTY_UT,0.003039476515581291 +4902,PIUTE_COUNTY_UT,0.0017581525460200708 +4902,JUAB_COUNTY_UT,0.001018456933821084 +4903,UTAH_COUNTY_UT,0.4849737866546685 +4903,SALT_LAKE_COUNTY_UT,0.317009086640975 +4903,UINTAH_COUNTY_UT,0.04355034331657505 +4903,WASATCH_COUNTY_UT,0.0425331090201295 +4903,CARBON_COUNTY_UT,0.024956474109430936 +4903,DUCHESNE_COUNTY_UT,0.023958802010993957 +4903,SUMMIT_COUNTY_UT,0.02029088988438741 +4903,SAN_JUAN_COUNTY_UT,0.01775024941802461 +4903,EMERY_COUNTY_UT,0.012012412214636437 +4903,GRAND_COUNTY_UT,0.011821680784052897 +4903,DAGGETT_COUNTY_UT,0.0011431659461257067 +4904,SALT_LAKE_COUNTY_UT,0.6306082376415814 +4904,UTAH_COUNTY_UT,0.32123207613607463 +4904,SANPETE_COUNTY_UT,0.03476813904810345 +4904,JUAB_COUNTY_UT,0.013391547174240497 +5000,CHITTENDEN_COUNTY_VT,0.2617462605566674 +5000,RUTLAND_COUNTY_VT,0.09419089782405528 +5000,WASHINGTON_COUNTY_VT,0.09300130466491571 +5000,WINDSOR_COUNTY_VT,0.08980728590821939 +5000,FRANKLIN_COUNTY_VT,0.07766721559004598 +5000,WINDHAM_COUNTY_VT,0.07138336466706165 +5000,ADDISON_COUNTY_VT,0.05810035190187178 +5000,BENNINGTON_COUNTY_VT,0.058075471522072784 +5000,CALEDONIA_COUNTY_VT,0.047013032653943465 +5000,ORANGE_COUNTY_VT,0.04552642996095335 +5000,ORLEANS_COUNTY_VT,0.04259676523962138 +5000,LAMOILLE_COUNTY_VT,0.0403450908678121 +5000,GRAND_ISLE_COUNTY_VT,0.011340788117130608 +5000,ESSEX_COUNTY_VT,0.009205740525629124 +5101,HENRICO_COUNTY_VA,0.24022314396792013 +5101,CHESTERFIELD_COUNTY_VA,0.2136399286709303 +5101,HANOVER_COUNTY_VA,0.1211436583592493 +5101,JAMES_CITY_COUNTY_VA,0.10178309667534217 +5101,YORK_COUNTY_VA,0.0911058477090544 +5101,GLOUCESTER_COUNTY_VA,0.0503504671377715 +5101,NEW_KENT_COUNTY_VA,0.02984400993196164 +5101,WESTMORELAND_COUNTY_VA,0.02403258973688626 +5101,KING_WILLIAM_COUNTY_VA,0.023165038870701104 +5101,POQUOSON_CITY_VA,0.016206422477761693 +5101,NORTHUMBERLAND_COUNTY_VA,0.015398702705796203 +5101,LANCASTER_COUNTY_VA,0.014202080821402884 +5101,MIDDLESEX_COUNTY_VA,0.01381968208878154 +5101,ESSEX_COUNTY_VA,0.013785864513787816 +5101,RICHMOND_COUNTY_VA,0.011605931602653899 +5101,MATHEWS_COUNTY_VA,0.011098667977748035 +5101,KING_AND_QUEEN_COUNTY_VA,0.008594866752251145 +5102,VIRGINIA_BEACH_CITY_VA,0.5849857022451167 +5102,CHESAPEAKE_CITY_VA,0.16443054703235646 +5102,SUFFOLK_CITY_VA,0.12009095561966698 +5102,ISLE_OF_WIGHT_COUNTY_VA,0.049152192789246256 +5102,ACCOMACK_COUNTY_VA,0.04254059518383373 +5102,NORTHAMPTON_COUNTY_VA,0.015637134948907488 +5102,SOUTHAMPTON_COUNTY_VA,0.01274830094800608 +5102,FRANKLIN_CITY_VA,0.010414571232866248 +5103,NORFOLK_CITY_VA,0.30529626584332475 +5103,NEWPORT_NEWS_CITY_VA,0.238904702105089 +5103,HAMPTON_CITY_VA,0.17592391869028087 +5103,CHESAPEAKE_CITY_VA,0.15427655925509276 +5103,PORTSMOUTH_CITY_VA,0.12559855410621265 +5104,RICHMOND_CITY_VA,0.2946036351912304 +5104,CHESTERFIELD_COUNTY_VA,0.26039290018369665 +5104,HENRICO_COUNTY_VA,0.19461442558076347 +5104,PRINCE_GEORGE_COUNTY_VA,0.055915018532169014 +5104,PETERSBURG_CITY_VA,0.04349697024062569 +5104,DINWIDDIE_COUNTY_VA,0.03633241160005876 +5104,HOPEWELL_CITY_VA,0.029943980977713294 +5104,BRUNSWICK_COUNTY_VA,0.020604443820421918 +5104,GREENSVILLE_COUNTY_VA,0.01480883459892902 +5104,SUSSEX_COUNTY_VA,0.014078208223316862 +5104,SOUTHAMPTON_COUNTY_VA,0.01037827465571507 +5104,CHARLES_CITY_COUNTY_VA,0.008805217868365047 +5104,SURRY_COUNTY_VA,0.00852960791884587 +5104,EMPORIA_CITY_VA,0.0074960706081489544 +5105,ALBEMARLE_COUNTY_VA,0.14238271194779703 +5105,LYNCHBURG_CITY_VA,0.10018716380891031 +5105,PITTSYLVANIA_COUNTY_VA,0.07671814094094195 +5105,CAMPBELL_COUNTY_VA,0.07062517277146994 +5105,CHARLOTTESVILLE_CITY_VA,0.059031414608414254 +5105,DANVILLE_CITY_VA,0.05400614242202142 +5105,LOUISA_COUNTY_VA,0.047673513277725225 +5105,HALIFAX_COUNTY_VA,0.043141511563325026 +5105,AMHERST_COUNTY_VA,0.039698762639263314 +5105,POWHATAN_COUNTY_VA,0.03846368438805296 +5105,MECKLENBURG_COUNTY_VA,0.03844593172325117 +5105,BEDFORD_COUNTY_VA,0.03547109232146525 +5105,FLUVANNA_COUNTY_VA,0.034553025941715464 +5105,GOOCHLAND_COUNTY_VA,0.03135501018242131 +5105,PRINCE_EDWARD_COUNTY_VA,0.027705569518167317 +5105,HANOVER_COUNTY_VA,0.02135391966158349 +5105,BUCKINGHAM_COUNTY_VA,0.02133363090181001 +5105,APPOMATTOX_COUNTY_VA,0.020439657424291224 +5105,NOTTOWAY_COUNTY_VA,0.019834798773544472 +5105,NELSON_COUNTY_VA,0.01873540160331924 +5105,AMELIA_COUNTY_VA,0.016820649899697445 +5105,LUNENBURG_COUNTY_VA,0.015135414791013093 +5105,CHARLOTTE_COUNTY_VA,0.014619319464275297 +5105,CUMBERLAND_COUNTY_VA,0.012268359425523767 +5106,ROANOKE_CITY_VA,0.1394200207434173 +5106,FREDERICK_COUNTY_VA,0.12744237010271336 +5106,ROCKINGHAM_COUNTY_VA,0.1167611830439292 +5106,AUGUSTA_COUNTY_VA,0.10802050921743786 +5106,ROANOKE_COUNTY_VA,0.07843883479987063 +5106,HARRISONBURG_CITY_VA,0.07223114412214081 +5106,SHENANDOAH_COUNTY_VA,0.06159735465667414 +5106,WARREN_COUNTY_VA,0.05677534656004996 +5106,BOTETOURT_COUNTY_VA,0.04683439838513612 +5106,STAUNTON_CITY_VA,0.03589670670369255 +5106,PAGE_COUNTY_VA,0.03305145705778045 +5106,ROCKBRIDGE_COUNTY_VA,0.03157516143062665 +5106,WAYNESBORO_CITY_VA,0.03094226415515184 +5106,ALLEGHANY_COUNTY_VA,0.021221575384478126 +5106,CLARKE_COUNTY_VA,0.020608194765075222 +5106,LEXINGTON_CITY_VA,0.01020442303188464 +5106,BATH_COUNTY_VA,0.005867543243333668 +5106,HIGHLAND_COUNTY_VA,0.0031115125966074473 +5107,PRINCE_WILLIAM_COUNTY_VA,0.35451863496697666 +5107,STAFFORD_COUNTY_VA,0.20024167781055727 +5107,SPOTSYLVANIA_COUNTY_VA,0.17868335357948573 +5107,CULPEPER_COUNTY_VA,0.06705729831259379 +5107,ORANGE_COUNTY_VA,0.04626075683180041 +5107,CAROLINE_COUNTY_VA,0.03941236818733986 +5107,FREDERICKSBURG_CITY_VA,0.03570553587652229 +5107,KING_GEORGE_COUNTY_VA,0.03409902920550015 +5107,GREENE_COUNTY_VA,0.02622472208327804 +5107,MADISON_COUNTY_VA,0.017656261165157564 +5107,ALBEMARLE_COUNTY_VA,0.0001403619807882729 +5108,FAIRFAX_COUNTY_VA,0.47357265018307776 +5108,ARLINGTON_COUNTY_VA,0.30435547824046383 +5108,ALEXANDRIA_CITY_VA,0.20337766055812256 +5108,FALLS_CHURCH_CITY_VA,0.018694211018335835 +5109,MONTGOMERY_COUNTY_VA,0.13015229963442346 +5109,FRANKLIN_COUNTY_VA,0.07110144129305247 +5109,WASHINGTON_COUNTY_VA,0.0703940421855239 +5109,BEDFORD_COUNTY_VA,0.06720161005081005 +5109,HENRY_COUNTY_VA,0.0664955161076865 +5109,ROANOKE_COUNTY_VA,0.0530705950375039 +5109,TAZEWELL_COUNTY_VA,0.05276649173113091 +5109,WISE_COUNTY_VA,0.04715558995388854 +5109,PULASKI_COUNTY_VA,0.04411455689015867 +5109,SMYTH_COUNTY_VA,0.03889389927002155 +5109,CARROLL_COUNTY_VA,0.03805206822877444 +5109,WYTHE_COUNTY_VA,0.036923101018419786 +5109,RUSSELL_COUNTY_VA,0.03364844352618877 +5109,LEE_COUNTY_VA,0.028939410352825094 +5109,SCOTT_COUNTY_VA,0.02816022720301963 +5109,BUCHANAN_COUNTY_VA,0.02656662146447277 +5109,PATRICK_COUNTY_VA,0.022981334843843605 +5109,BRISTOL_CITY_VA,0.02247362589028527 +5109,GILES_COUNTY_VA,0.02190979486731046 +5109,RADFORD_CITY_VA,0.020973991988900882 +5109,FLOYD_COUNTY_VA,0.02019872433231052 +5109,GRAYSON_COUNTY_VA,0.020012085822390616 +5109,DICKENSON_COUNTY_VA,0.018434142056704173 +5109,BLAND_COUNTY_VA,0.008183380819564937 +5109,CRAIG_COUNTY_VA,0.0063848642694276985 +5109,NORTON_CITY_VA,0.004812141161361391 +5110,LOUDOUN_COUNTY_VA,0.5677104475639343 +5110,PRINCE_WILLIAM_COUNTY_VA,0.2756185747056991 +5110,FAUQUIER_COUNTY_VA,0.09841093023224451 +5110,FAIRFAX_COUNTY_VA,0.02512869132019695 +5110,MANASSAS_PARK_CITY_VA,0.023221753654401937 +5110,RAPPAHANNOCK_COUNTY_VA,0.00990960252352317 +5111,FAIRFAX_COUNTY_VA,0.9692210422729469 +5111,FAIRFAX_CITY_VA,0.030778957727053132 +5301,SNOHOMISH_COUNTY_WA,0.5956289319988446 +5301,KING_COUNTY_WA,0.4043710680011554 +5302,SNOHOMISH_COUNTY_WA,0.4005824952379714 +5302,WHATCOM_COUNTY_WA,0.2949493242145091 +5302,SKAGIT_COUNTY_WA,0.16840743461555965 +5302,ISLAND_COUNTY_WA,0.11293256447429155 +5302,SAN_JUAN_COUNTY_WA,0.023128181457668328 +5303,CLARK_COUNTY_WA,0.6542386144431099 +5303,COWLITZ_COUNTY_WA,0.14393454897128327 +5303,LEWIS_COUNTY_WA,0.10678297898890951 +5303,THURSTON_COUNTY_WA,0.0432791547728608 +5303,PACIFIC_COUNTY_WA,0.03037145070634908 +5303,SKAMANIA_COUNTY_WA,0.015645229218986412 +5303,WAHKIAKUM_COUNTY_WA,0.005748022898500991 +5304,YAKIMA_COUNTY_WA,0.3339724993820817 +5304,BENTON_COUNTY_WA,0.2691170922714678 +5304,GRANT_COUNTY_WA,0.12894719725253997 +5304,FRANKLIN_COUNTY_WA,0.1084778394973397 +5304,DOUGLAS_COUNTY_WA,0.0550753860363466 +5304,OKANOGAN_COUNTY_WA,0.0547722808341247 +5304,KLICKITAT_COUNTY_WA,0.02957552262881971 +5304,ADAMS_COUNTY_WA,0.02006218209727986 +5305,SPOKANE_COUNTY_WA,0.6960371468283107 +5305,WALLA_WALLA_COUNTY_WA,0.08076699218321501 +5305,WHITMAN_COUNTY_WA,0.061910950338830595 +5305,STEVENS_COUNTY_WA,0.05993900920282215 +5305,ASOTIN_COUNTY_WA,0.02875962579577762 +5305,PEND_OREILLE_COUNTY_WA,0.017294491599246842 +5305,FRANKLIN_COUNTY_WA,0.017242870103539816 +5305,LINCOLN_COUNTY_WA,0.014035884682740741 +5305,FERRY_COUNTY_WA,0.009263477404626061 +5305,ADAMS_COUNTY_WA,0.006699179605379476 +5305,COLUMBIA_COUNTY_WA,0.005100203775854304 +5305,GARFIELD_COUNTY_WA,0.002950168479656614 +5306,KITSAP_COUNTY_WA,0.35641903687936294 +5306,PIERCE_COUNTY_WA,0.3183499336590463 +5306,CLALLAM_COUNTY_WA,0.09977653573488447 +5306,GRAYS_HARBOR_COUNTY_WA,0.09781217104327293 +5306,MASON_COUNTY_WA,0.08499659889457607 +5306,JEFFERSON_COUNTY_WA,0.04264572378885731 +5307,KING_COUNTY_WA,1.0 +5308,KING_COUNTY_WA,0.5442471036452697 +5308,PIERCE_COUNTY_WA,0.21669890250919988 +5308,CHELAN_COUNTY_WA,0.10271391588198694 +5308,SNOHOMISH_COUNTY_WA,0.07796744028358882 +5308,KITTITAS_COUNTY_WA,0.05759196307837791 +5308,DOUGLAS_COUNTY_WA,0.0007806746015766769 +5309,KING_COUNTY_WA,1.0 +5310,PIERCE_COUNTY_WA,0.6602289411795278 +5310,THURSTON_COUNTY_WA,0.33977105882047215 +5401,KANAWHA_COUNTY_WV,0.2017092471879893 +5401,CABELL_COUNTY_WV,0.10529346577878664 +5401,RALEIGH_COUNTY_WV,0.08324265931007391 +5401,MERCER_COUNTY_WV,0.06658430675384765 +5401,PUTNAM_COUNTY_WV,0.0641023494894913 +5401,FAYETTE_COUNTY_WV,0.04518412127664561 +5401,WAYNE_COUNTY_WV,0.0435034433809079 +5401,GREENBRIER_COUNTY_WV,0.03680193556955005 +5401,LOGAN_COUNTY_WV,0.03634438049833327 +5401,JACKSON_COUNTY_WV,0.031014421912647156 +5401,MASON_COUNTY_WV,0.028405242018732974 +5401,NICHOLAS_COUNTY_WV,0.02745776822492068 +5401,MINGO_COUNTY_WV,0.026301604679114396 +5401,BOONE_COUNTY_WV,0.024338581824796583 +5401,WYOMING_COUNTY_WV,0.023862054957943992 +5401,LINCOLN_COUNTY_WV,0.022836462005631275 +5401,MCDOWELL_COUNTY_WV,0.021327646258594502 +5401,ROANE_COUNTY_WV,0.015655079363485095 +5401,BRAXTON_COUNTY_WV,0.013890702369354078 +5401,MONROE_COUNTY_WV,0.013811467222875075 +5401,SUMMERS_COUNTY_WV,0.01334610023580826 +5401,WEBSTER_COUNTY_WV,0.009349747284522252 +5401,CLAY_COUNTY_WV,0.00898481921552741 +5401,POCAHONTAS_COUNTY_WV,0.008781709403426307 +5401,GILMER_COUNTY_WV,0.008267238945302081 +5401,CALHOUN_COUNTY_WV,0.0069514891185592145 +5401,PENDLETON_COUNTY_WV,0.006855514152401551 +5401,WIRT_COUNTY_WV,0.0057964415607315075 +5402,BERKELEY_COUNTY_WV,0.13599524981367997 +5402,MONONGALIA_COUNTY_WV,0.1178879495214722 +5402,WOOD_COUNTY_WV,0.09390752955776702 +5402,HARRISON_COUNTY_WV,0.0734373903385399 +5402,JEFFERSON_COUNTY_WV,0.06428013622251014 +5402,MARION_COUNTY_WV,0.06261356053424 +5402,OHIO_COUNTY_WV,0.04726234864629716 +5402,PRESTON_COUNTY_WV,0.03811734876326939 +5402,MARSHALL_COUNTY_WV,0.0340790219785239 +5402,HANCOCK_COUNTY_WV,0.03241244629025376 +5402,RANDOLPH_COUNTY_WV,0.03111683965558921 +5402,MINERAL_COUNTY_WV,0.03000950260068245 +5402,UPSHUR_COUNTY_WV,0.02653152847048234 +5402,HAMPSHIRE_COUNTY_WV,0.02572609115589724 +5402,BROOKE_COUNTY_WV,0.025131203844709904 +5402,MORGAN_COUNTY_WV,0.019008543428444748 +5402,LEWIS_COUNTY_WV,0.018975122792984786 +5402,TAYLOR_COUNTY_WV,0.018609723845289194 +5402,BARBOUR_COUNTY_WV,0.017228337579610738 +5402,WETZEL_COUNTY_WV,0.016088693910426013 +5402,HARDY_COUNTY_WV,0.01592938888140019 +5402,GRANT_COUNTY_WV,0.01222749649361833 +5402,RITCHIE_COUNTY_WV,0.009406794860797483 +5402,TYLER_COUNTY_WV,0.009260858085955646 +5402,DODDRIDGE_COUNTY_WV,0.008698277389046275 +5402,PLEASANTS_COUNTY_WV,0.008525604105836469 +5402,TUCKER_COUNTY_WV,0.0075330112326755785 +5501,RACINE_COUNTY_WI,0.26839008300360384 +5501,KENOSHA_COUNTY_WI,0.2296016777179778 +5501,MILWAUKEE_COUNTY_WI,0.19552608539258737 +5501,ROCK_COUNTY_WI,0.17373204020550687 +5501,WALWORTH_COUNTY_WI,0.13275011368032413 +5502,DANE_COUNTY_WI,0.7621726176336847 +5502,SAUK_COUNTY_WI,0.08429582674439913 +5502,GREEN_COUNTY_WI,0.05034918523445294 +5502,ROCK_COUNTY_WI,0.04845292955892034 +5502,IOWA_COUNTY_WI,0.032182051403867165 +5502,LAFAYETTE_COUNTY_WI,0.022547389424675757 +5503,LA_CROSSE_COUNTY_WI,0.16394920159192958 +5503,EAU_CLAIRE_COUNTY_WI,0.14348812839683134 +5503,PORTAGE_COUNTY_WI,0.09552799179059501 +5503,GRANT_COUNTY_WI,0.07049935117467246 +5503,DUNN_COUNTY_WI,0.06167912737065572 +5503,WOOD_COUNTY_WI,0.05948017960788146 +5503,PIERCE_COUNTY_WI,0.05729752034705368 +5503,MONROE_COUNTY_WI,0.05645187562099914 +5503,CHIPPEWA_COUNTY_WI,0.04765879932022652 +5503,TREMPEALEAU_COUNTY_WI,0.04175285998946677 +5503,VERNON_COUNTY_WI,0.041690420732005275 +5503,JUNEAU_COUNTY_WI,0.02824154762486494 +5503,ADAMS_COUNTY_WI,0.02803522660020958 +5503,JACKSON_COUNTY_WI,0.02590957709619446 +5503,RICHLAND_COUNTY_WI,0.023488019806818368 +5503,CRAWFORD_COUNTY_WI,0.021871385988630625 +5503,BUFFALO_COUNTY_WI,0.018076165035101722 +5503,PEPIN_COUNTY_WI,0.009933271437025937 +5503,SAUK_COUNTY_WI,0.004969350468837381 +5504,MILWAUKEE_COUNTY_WI,1.0 +5505,WAUKESHA_COUNTY_WI,0.5524225786090958 +5505,WASHINGTON_COUNTY_WI,0.18563623653651684 +5505,JEFFERSON_COUNTY_WI,0.1152413076970063 +5505,MILWAUKEE_COUNTY_WI,0.07971603673062175 +5505,DODGE_COUNTY_WI,0.0552031654031749 +5505,WALWORTH_COUNTY_WI,0.011780675023584425 +5506,WINNEBAGO_COUNTY_WI,0.22927893320881645 +5506,SHEBOYGAN_COUNTY_WI,0.16021685484462084 +5506,FOND_DU_LAC_COUNTY_WI,0.141376436446165 +5506,OZAUKEE_COUNTY_WI,0.12420423665085772 +5506,MANITOWOC_COUNTY_WI,0.11043498562535801 +5506,COLUMBIA_COUNTY_WI,0.07939308876986184 +5506,DODGE_COUNTY_WI,0.06614099908512666 +5506,WAUSHARA_COUNTY_WI,0.03328292933214246 +5506,GREEN_LAKE_COUNTY_WI,0.02581463091511767 +5506,MARQUETTE_COUNTY_WI,0.021164250984778355 +5506,CALUMET_COUNTY_WI,0.008692654137154988 +5507,MARATHON_COUNTY_WI,0.1873356725463714 +5507,ST_CROIX_COUNTY_WI,0.12696361550938964 +5507,BARRON_COUNTY_WI,0.06340443726542828 +5507,POLK_COUNTY_WI,0.06105074553931982 +5507,DOUGLAS_COUNTY_WI,0.060125014422130675 +5507,ONEIDA_COUNTY_WI,0.05136993274196942 +5507,CLARK_COUNTY_WI,0.047045329605071164 +5507,CHIPPEWA_COUNTY_WI,0.04233115926783084 +5507,WOOD_COUNTY_WI,0.041246615041094586 +5507,LINCOLN_COUNTY_WI,0.038569867587873195 +5507,VILAS_COUNTY_WI,0.03128346782677155 +5507,TAYLOR_COUNTY_WI,0.02702944829411645 +5507,LANGLADE_COUNTY_WI,0.02645663519814311 +5507,SAWYER_COUNTY_WI,0.024533231982516984 +5507,WASHBURN_COUNTY_WI,0.02256367794873187 +5507,BURNETT_COUNTY_WI,0.022432012379278284 +5507,BAYFIELD_COUNTY_WI,0.022016655015847375 +5507,ASHLAND_COUNTY_WI,0.021754681253944877 +5507,RUSK_COUNTY_WI,0.019258464942345413 +5507,PRICE_COUNTY_WI,0.019076576423718804 +5507,FOREST_COUNTY_WI,0.012459363525922508 +5507,IRON_COUNTY_WI,0.008330222677697617 +5507,JUNEAU_COUNTY_WI,0.00802481285164548 +5507,MONROE_COUNTY_WI,0.006359311266907828 +5507,FLORENCE_COUNTY_WI,0.006186924387313954 +5507,JACKSON_COUNTY_WI,0.002792124498618869 +5508,BROWN_COUNTY_WI,0.3647819913833591 +5508,OUTAGAMIE_COUNTY_WI,0.258858933045931 +5508,WAUPACA_COUNTY_WI,0.07032851282858749 +5508,CALUMET_COUNTY_WI,0.06249100736513762 +5508,MARINETTE_COUNTY_WI,0.05683616708790657 +5508,SHAWANO_COUNTY_WI,0.05549100465037993 +5508,OCONTO_COUNTY_WI,0.052890266779238616 +5508,DOOR_COUNTY_WI,0.04081095241844189 +5508,KEWAUNEE_COUNTY_WI,0.02791178123396596 +5508,MENOMINEE_COUNTY_WI,0.005775646994627495 +5508,WINNEBAGO_COUNTY_WI,0.00382373621242436 +5600,LARAMIE_COUNTY_WY,0.17424256870491686 +5600,NATRONA_COUNTY_WY,0.13860598317416456 +5600,CAMPBELL_COUNTY_WY,0.08152191813830607 +5600,SWEETWATER_COUNTY_WY,0.07328062185902426 +5600,FREMONT_COUNTY_WY,0.06801409722788034 +5600,ALBANY_COUNTY_WY,0.0642557610197434 +5600,SHERIDAN_COUNTY_WY,0.05360309681356191 +5600,PARK_COUNTY_WY,0.051354682578343455 +5600,TETON_COUNTY_WY,0.040445452985259624 +5600,UINTA_COUNTY_WY,0.03545109569022156 +5600,LINCOLN_COUNTY_WY,0.03394464081712609 +5600,CARBON_COUNTY_WY,0.0252006150635086 +5600,CONVERSE_COUNTY_WY,0.023838044833067812 +5600,GOSHEN_COUNTY_WY,0.02166590679395546 +5600,BIG_HORN_COUNTY_WY,0.019972228530417732 +5600,SUBLETTE_COUNTY_WY,0.015130423627591873 +5600,PLATTE_COUNTY_WY,0.014917196988477094 +5600,JOHNSON_COUNTY_WY,0.014643296102459733 +5600,WASHAKIE_COUNTY_WY,0.013322331069895 +5600,CROOK_COUNTY_WY,0.01244862191449785 +5600,WESTON_COUNTY_WY,0.01185401429485257 +5600,HOT_SPRINGS_COUNTY_WY,0.008010734141052023 +5600,NIOBRARA_COUNTY_WY,0.004276667631676117 diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_county_assignment.py b/policyengine_us_data/tests/test_local_area_calibration/test_county_assignment.py new file mode 100644 index 00000000..a5459cc1 --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/test_county_assignment.py @@ -0,0 +1,114 @@ +"""Tests for county assignment functionality.""" + +import pytest +import numpy as np + +from policyengine_us.variables.household.demographic.geographic.county.county_enum import ( + County, +) +from policyengine_us_data.datasets.cps.local_area_calibration.county_assignment import ( + assign_counties_for_cd, + get_county_index, + _build_state_counties, +) + + +class TestCountyAssignment: + """Test county assignment for CDs.""" + + def test_assign_returns_correct_shape(self): + """Verify assign_counties_for_cd returns correct shape.""" + n_households = 100 + result = assign_counties_for_cd("3610", n_households, seed=42) + assert result.shape == (n_households,) + assert result.dtype == np.int32 + + def test_assign_is_deterministic(self): + """Verify same seed produces same results.""" + result1 = assign_counties_for_cd("3610", 50, seed=42) + result2 = assign_counties_for_cd("3610", 50, seed=42) + np.testing.assert_array_equal(result1, result2) + + def test_different_seeds_different_results(self): + """Verify different seeds produce different results.""" + result1 = assign_counties_for_cd("3610", 100, seed=42) + result2 = assign_counties_for_cd("3610", 100, seed=99) + # With 100 samples, should be different + assert not np.array_equal(result1, result2) + + def test_ny_cd_gets_ny_counties(self): + """Verify NY CDs get NY counties.""" + # NY-10 (Manhattan/Brooklyn area) + result = assign_counties_for_cd("3610", 100, seed=42) + + # All indices should be valid County enum indices + for idx in result: + county_name = County._member_names_[idx] + # Should end with _NY + assert county_name.endswith( + "_NY" + ), f"Got non-NY county: {county_name}" + + def test_ca_cd_gets_ca_counties(self): + """Verify CA CDs get CA counties.""" + # CA-12 (San Francisco area) + result = assign_counties_for_cd("612", 100, seed=42) + + for idx in result: + county_name = County._member_names_[idx] + assert county_name.endswith( + "_CA" + ), f"Got non-CA county: {county_name}" + + +class TestCountyIndex: + """Test county index conversion.""" + + def test_get_county_index_known_county(self): + """Verify known county returns valid index.""" + idx = get_county_index("NEW_YORK_COUNTY_NY") + assert isinstance(idx, int) + assert idx >= 0 + assert County._member_names_[idx] == "NEW_YORK_COUNTY_NY" + + def test_get_county_index_unknown(self): + """Verify UNKNOWN county returns valid index.""" + idx = get_county_index("UNKNOWN") + assert isinstance(idx, int) + assert idx >= 0 + + +class TestStateCuntiesMapping: + """Test state to counties mapping.""" + + def test_build_state_counties_excludes_unknown(self): + """Verify UNKNOWN is not in any state's county list.""" + state_counties = _build_state_counties() + for state, counties in state_counties.items(): + assert "UNKNOWN" not in counties + + def test_all_50_states_plus_dc(self): + """Verify we have counties for all 50 states + DC.""" + state_counties = _build_state_counties() + # Check for some known states + assert "NY" in state_counties + assert "CA" in state_counties + assert "TX" in state_counties + assert "DC" in state_counties + # Should have 51 (50 states + DC) + assert len(state_counties) >= 51 + + def test_ny_has_nyc_counties(self): + """Verify NY includes NYC counties.""" + state_counties = _build_state_counties() + ny_counties = state_counties["NY"] + + nyc_counties = [ + "QUEENS_COUNTY_NY", + "BRONX_COUNTY_NY", + "RICHMOND_COUNTY_NY", + "NEW_YORK_COUNTY_NY", + "KINGS_COUNTY_NY", + ] + for county in nyc_counties: + assert county in ny_counties, f"Missing NYC county: {county}" From dc44624a6cefbab36eb883c487e3e1df474dda5d Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 10 Dec 2025 09:21:22 -0500 Subject: [PATCH 08/24] Add local area H5 publishing workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New GitHub Actions workflow (local_area_publish.yaml) that: - Triggers on local_area_calibration/ changes, repository_dispatch, or manual - Downloads calibration inputs from HF calibration/ folder - Builds 51 state + 436 district H5 files with checkpointing - Uploads to GCP and HF states/ and districts/ subdirectories - New publish_local_area.py script with: - Per-state and per-district checkpointing for spot instance resilience - Immediate upload after each file is built - Support for --states-only, --districts-only, --skip-download flags - Added upload_local_area_file() to data_upload.py for subdirectory uploads - Added download_calibration_inputs() to huggingface.py - Added publish-local-area Makefile target 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/local_area_publish.yaml | 67 +++++ Makefile | 5 +- .../publish_local_area.py | 264 ++++++++++++++++++ .../stacked_dataset_builder.py | 16 +- policyengine_us_data/utils/data_upload.py | 51 ++++ policyengine_us_data/utils/huggingface.py | 44 +++ 6 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/local_area_publish.yaml create mode 100644 policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py diff --git a/.github/workflows/local_area_publish.yaml b/.github/workflows/local_area_publish.yaml new file mode 100644 index 00000000..6bf02987 --- /dev/null +++ b/.github/workflows/local_area_publish.yaml @@ -0,0 +1,67 @@ +name: Publish Local Area H5 Files + +on: + push: + branches: [main] + paths: + - 'policyengine_us_data/datasets/cps/local_area_calibration/**' + - '.github/workflows/local_area_publish.yaml' + repository_dispatch: + types: [calibration-updated] + workflow_dispatch: + +# Trigger strategy: +# 1. Automatic: Code changes to local_area_calibration/ pushed to main +# 2. repository_dispatch: Calibration workflow triggers after uploading new weights +# 3. workflow_dispatch: Manual trigger when you update weights/data on HF yourself + +jobs: + publish-local-area: + runs-on: self-hosted + permissions: + contents: read + id-token: write + env: + HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: "projects/322898545428/locations/global/workloadIdentityPools/policyengine-research-id-pool/providers/prod-github-provider" + service_account: "policyengine-research@policyengine-research.iam.gserviceaccount.com" + + - name: Install package + run: uv pip install -e .[dev] --system + + - name: Download checkpoint (if exists) + continue-on-error: true + run: | + gsutil cp gs://policyengine-us-data/checkpoints/completed_states.txt . || true + gsutil cp gs://policyengine-us-data/checkpoints/completed_districts.txt . || true + + - name: Build and publish local area H5 files + run: make publish-local-area + + - name: Upload checkpoint + if: always() + run: | + gsutil cp completed_states.txt gs://policyengine-us-data/checkpoints/ || true + gsutil cp completed_districts.txt gs://policyengine-us-data/checkpoints/ || true + + - name: Clean up checkpoints on success + if: success() + run: | + gsutil rm gs://policyengine-us-data/checkpoints/completed_states.txt || true + gsutil rm gs://policyengine-us-data/checkpoints/completed_districts.txt || true diff --git a/Makefile b/Makefile index fde07f6b..d096f1ef 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all format test install download upload docker documentation data clean build paper clean-paper presentations +.PHONY: all format test install download upload docker documentation data data-local-area publish-local-area clean build paper clean-paper presentations all: data test @@ -80,6 +80,9 @@ data-local-area: data LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/extended_cps.py python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 +publish-local-area: + python policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py + clean: rm -f policyengine_us_data/storage/*.h5 rm -f policyengine_us_data/storage/*.db diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py b/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py new file mode 100644 index 00000000..eefc36ab --- /dev/null +++ b/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py @@ -0,0 +1,264 @@ +""" +Publish local area H5 files to GCP and Hugging Face. + +Downloads calibration inputs from HF, generates state/district H5s +with checkpointing, and uploads to both destinations. + +Usage: + python publish_local_area.py [--skip-download] [--states-only] [--districts-only] +""" + +import os +import numpy as np +from pathlib import Path + +from policyengine_us import Microsimulation +from policyengine_us_data.utils.huggingface import download_calibration_inputs +from policyengine_us_data.utils.data_upload import upload_local_area_file +from policyengine_us_data.datasets.cps.local_area_calibration.stacked_dataset_builder import ( + create_sparse_cd_stacked_dataset, +) +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_all_cds_from_database, + STATE_CODES, +) + +CHECKPOINT_FILE = Path("completed_states.txt") +CHECKPOINT_FILE_DISTRICTS = Path("completed_districts.txt") +WORK_DIR = Path("local_area_build") + + +def load_completed_states() -> set: + if CHECKPOINT_FILE.exists(): + content = CHECKPOINT_FILE.read_text().strip() + if content: + return set(content.split("\n")) + return set() + + +def record_completed_state(state_code: str): + with open(CHECKPOINT_FILE, "a") as f: + f.write(f"{state_code}\n") + + +def load_completed_districts() -> set: + if CHECKPOINT_FILE_DISTRICTS.exists(): + content = CHECKPOINT_FILE_DISTRICTS.read_text().strip() + if content: + return set(content.split("\n")) + return set() + + +def record_completed_district(district_name: str): + with open(CHECKPOINT_FILE_DISTRICTS, "a") as f: + f.write(f"{district_name}\n") + + +def build_and_upload_states( + weights_path: Path, + dataset_path: Path, + db_path: Path, + output_dir: Path, + completed_states: set, +): + """Build and upload state H5 files with checkpointing.""" + db_uri = f"sqlite:///{db_path}" + cds_to_calibrate = get_all_cds_from_database(db_uri) + w = np.load(weights_path) + + states_dir = output_dir / "states" + states_dir.mkdir(parents=True, exist_ok=True) + + for state_fips, state_code in STATE_CODES.items(): + if state_code in completed_states: + print(f"Skipping {state_code} (already completed)") + continue + + cd_subset = [ + cd for cd in cds_to_calibrate if int(cd) // 100 == state_fips + ] + if not cd_subset: + print(f"No CDs found for {state_code}, skipping") + continue + + output_path = states_dir / f"{state_code}.h5" + print(f"\n{'='*60}") + print(f"Building {state_code} ({len(cd_subset)} CDs)") + print(f"{'='*60}") + + try: + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=cd_subset, + dataset_path=str(dataset_path), + output_path=str(output_path), + ) + + print(f"Uploading {state_code}.h5...") + upload_local_area_file(str(output_path), "states") + + record_completed_state(state_code) + print(f"Completed {state_code}") + + except Exception as e: + print(f"ERROR building {state_code}: {e}") + raise + + +def build_and_upload_districts( + weights_path: Path, + dataset_path: Path, + db_path: Path, + output_dir: Path, + completed_districts: set, +): + """Build and upload district H5 files with checkpointing.""" + db_uri = f"sqlite:///{db_path}" + cds_to_calibrate = get_all_cds_from_database(db_uri) + w = np.load(weights_path) + + districts_dir = output_dir / "districts" + districts_dir.mkdir(parents=True, exist_ok=True) + + for i, cd_geoid in enumerate(cds_to_calibrate): + cd_int = int(cd_geoid) + state_fips = cd_int // 100 + district_num = cd_int % 100 + state_code = STATE_CODES.get(state_fips, str(state_fips)) + friendly_name = f"{state_code}-{district_num:02d}" + + if friendly_name in completed_districts: + print(f"Skipping {friendly_name} (already completed)") + continue + + output_path = districts_dir / f"{friendly_name}.h5" + print(f"\n{'='*60}") + print(f"[{i+1}/{len(cds_to_calibrate)}] Building {friendly_name}") + print(f"{'='*60}") + + try: + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=[cd_geoid], + dataset_path=str(dataset_path), + output_path=str(output_path), + ) + + print(f"Uploading {friendly_name}.h5...") + upload_local_area_file(str(output_path), "districts") + + record_completed_district(friendly_name) + print(f"Completed {friendly_name}") + + except Exception as e: + print(f"ERROR building {friendly_name}: {e}") + raise + + +def main(): + import argparse + + parser = argparse.ArgumentParser( + description="Build and publish local area H5 files" + ) + parser.add_argument( + "--skip-download", + action="store_true", + help="Skip downloading inputs from HF (use existing files)", + ) + parser.add_argument( + "--states-only", + action="store_true", + help="Only build and upload state files", + ) + parser.add_argument( + "--districts-only", + action="store_true", + help="Only build and upload district files", + ) + parser.add_argument( + "--weights-path", + type=str, + help="Override path to weights file (for local testing)", + ) + parser.add_argument( + "--dataset-path", + type=str, + help="Override path to dataset file (for local testing)", + ) + parser.add_argument( + "--db-path", + type=str, + help="Override path to database file (for local testing)", + ) + args = parser.parse_args() + + WORK_DIR.mkdir(parents=True, exist_ok=True) + + if args.weights_path and args.dataset_path and args.db_path: + inputs = { + "weights": Path(args.weights_path), + "dataset": Path(args.dataset_path), + "database": Path(args.db_path), + } + print("Using provided paths:") + for key, path in inputs.items(): + print(f" {key}: {path}") + elif args.skip_download: + inputs = { + "weights": WORK_DIR / "w_district_calibration.npy", + "dataset": WORK_DIR / "stratified_extended_cps.h5", + "database": WORK_DIR / "policy_data.db", + } + print("Using existing files in work directory:") + for key, path in inputs.items(): + if not path.exists(): + raise FileNotFoundError(f"Expected file not found: {path}") + print(f" {key}: {path}") + else: + print("Downloading calibration inputs from Hugging Face...") + inputs = download_calibration_inputs(str(WORK_DIR)) + for key, path in inputs.items(): + inputs[key] = Path(path) + + sim = Microsimulation(dataset=str(inputs["dataset"])) + n_hh = sim.calculate("household_id", map_to="household").shape[0] + print(f"\nBase dataset has {n_hh:,} households") + + if not args.districts_only: + print("\n" + "=" * 60) + print("BUILDING STATE FILES") + print("=" * 60) + completed_states = load_completed_states() + print(f"Already completed: {len(completed_states)} states") + build_and_upload_states( + inputs["weights"], + inputs["dataset"], + inputs["database"], + WORK_DIR, + completed_states, + ) + + if not args.states_only: + print("\n" + "=" * 60) + print("BUILDING DISTRICT FILES") + print("=" * 60) + completed_districts = load_completed_districts() + print(f"Already completed: {len(completed_districts)} districts") + build_and_upload_districts( + inputs["weights"], + inputs["dataset"], + inputs["database"], + WORK_DIR, + completed_districts, + ) + + print("\n" + "=" * 60) + print("ALL DONE!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py index 25cb1f8e..ec21a908 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -37,11 +37,17 @@ def create_sparse_cd_stacked_dataset( Create a SPARSE congressional district-stacked dataset using DataFrame approach. Args: - w: Calibrated weight vector from L0 calibration (length = n_households * n_cds) - cds_to_calibrate: List of CD GEOID codes used in calibration - cd_subset: Optional list of CD GEOIDs to include (subset of cds_to_calibrate) - output_path: Where to save the sparse CD-stacked h5 file - dataset_path: Path to the base .h5 dataset used to create the training matrices + w: Calibrated weight vector from L0 calibration. Shape is (n_cds * n_households,), + reshaped internally to (n_cds, n_households) using cds_to_calibrate ordering. + cds_to_calibrate: Ordered list of CD GEOID codes that defines the row ordering + of the weight matrix. Required to correctly index into w for any cd_subset. + cd_subset: Optional list of CD GEOIDs to include in output (must be subset of + cds_to_calibrate). If None, includes all CDs. + output_path: Where to save the sparse CD-stacked .h5 file. + dataset_path: Path to the base .h5 dataset used during calibration. + + Returns: + output_path: Path to the saved .h5 file. """ # Handle CD subset filtering diff --git a/policyengine_us_data/utils/data_upload.py b/policyengine_us_data/utils/data_upload.py index f0f0c24b..20aa1b65 100644 --- a/policyengine_us_data/utils/data_upload.py +++ b/policyengine_us_data/utils/data_upload.py @@ -116,3 +116,54 @@ def upload_files_to_gcs( logging.info( f"Set metadata for {file_path.name} in GCS bucket {gcs_bucket_name}." ) + + +def upload_local_area_file( + file_path: str, + subdirectory: str, + gcs_bucket_name: str = "policyengine-us-data", + hf_repo_name: str = "policyengine/policyengine-us-data", + hf_repo_type: str = "model", + version: str = None, +): + """ + Upload a single local area H5 file to a subdirectory (states/ or districts/). + + Uploads to both GCS and Hugging Face with the file placed in the specified + subdirectory. + """ + if version is None: + version = metadata.version("policyengine-us-data") + + file_path = Path(file_path) + if not file_path.exists(): + raise ValueError(f"File {file_path} does not exist.") + + # Upload to GCS with subdirectory + credentials, project_id = google.auth.default() + storage_client = storage.Client( + credentials=credentials, project=project_id + ) + bucket = storage_client.bucket(gcs_bucket_name) + + blob_name = f"{subdirectory}/{file_path.name}" + blob = bucket.blob(blob_name) + blob.upload_from_filename(file_path) + blob.metadata = {"version": version} + blob.patch() + logging.info(f"Uploaded {blob_name} to GCS bucket {gcs_bucket_name}.") + + # Upload to Hugging Face with subdirectory + token = os.environ.get("HUGGING_FACE_TOKEN") + api = HfApi() + api.upload_file( + path_or_fileobj=str(file_path), + path_in_repo=f"{subdirectory}/{file_path.name}", + repo_id=hf_repo_name, + repo_type=hf_repo_type, + token=token, + commit_message=f"Upload {subdirectory}/{file_path.name} for version {version}", + ) + logging.info( + f"Uploaded {subdirectory}/{file_path.name} to Hugging Face {hf_repo_name}." + ) diff --git a/policyengine_us_data/utils/huggingface.py b/policyengine_us_data/utils/huggingface.py index 7b8c0792..a6c602e6 100644 --- a/policyengine_us_data/utils/huggingface.py +++ b/policyengine_us_data/utils/huggingface.py @@ -34,3 +34,47 @@ def upload(local_file_path: str, repo: str, repo_file_path: str): repo_type="model", token=TOKEN, ) + + +def download_calibration_inputs( + output_dir: str, + repo: str = "policyengine/policyengine-us-data", + version: str = None, +) -> dict: + """ + Download calibration inputs from Hugging Face. + + Args: + output_dir: Local directory to download files to + repo: Hugging Face repository ID + version: Optional revision (commit, tag, or branch) + + Returns: + dict with keys 'weights', 'dataset', 'database' mapping to local paths + """ + from pathlib import Path + + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + + files = { + "weights": "calibration/w_district_calibration.npy", + "dataset": "calibration/stratified_extended_cps.h5", + "database": "calibration/policy_data.db", + } + + paths = {} + for key, hf_path in files.items(): + local_path = output_path / Path(hf_path).name + hf_hub_download( + repo_id=repo, + filename=hf_path, + local_dir=str(output_path), + repo_type="model", + revision=version, + token=TOKEN, + ) + paths[key] = local_path + print(f"Downloaded {hf_path} to {local_path}") + + return paths From 7287828488bc72282c0ea0a33f6080d7528aa58d Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 10 Dec 2025 10:19:29 -0500 Subject: [PATCH 09/24] Update paths to use calibration/ subdirectory for policy_data.db MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - download_private_prerequisites.py: Download from calibration/policy_data.db - calibration_utils.py: Look for db in storage/calibration/ - conftest.py: Update test fixture path - huggingface.py: Fix download_calibration_inputs to return correct paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../datasets/cps/local_area_calibration/calibration_utils.py | 2 +- policyengine_us_data/storage/download_private_prerequisites.py | 2 +- .../tests/test_local_area_calibration/conftest.py | 2 +- policyengine_us_data/utils/huggingface.py | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index abf36057..e7f41f85 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -397,7 +397,7 @@ def get_cd_index_mapping(db_uri: str = None): from policyengine_us_data.storage import STORAGE_FOLDER if db_uri is None: - db_path = STORAGE_FOLDER / "calibration_targets" / "policy_data.db" + db_path = STORAGE_FOLDER / "calibration" / "policy_data.db" db_uri = f"sqlite:///{db_path}" engine = create_engine(db_uri) diff --git a/policyengine_us_data/storage/download_private_prerequisites.py b/policyengine_us_data/storage/download_private_prerequisites.py index 3e080274..94586a81 100644 --- a/policyengine_us_data/storage/download_private_prerequisites.py +++ b/policyengine_us_data/storage/download_private_prerequisites.py @@ -29,7 +29,7 @@ ) download( repo="policyengine/policyengine-us-data", - repo_filename="policy_data.db", + repo_filename="calibration/policy_data.db", local_folder=FOLDER, version=None, ) diff --git a/policyengine_us_data/tests/test_local_area_calibration/conftest.py b/policyengine_us_data/tests/test_local_area_calibration/conftest.py index 542036b4..04d6d7f5 100644 --- a/policyengine_us_data/tests/test_local_area_calibration/conftest.py +++ b/policyengine_us_data/tests/test_local_area_calibration/conftest.py @@ -19,7 +19,7 @@ @pytest.fixture(scope="module") def db_uri(): - db_path = STORAGE_FOLDER / "policy_data.db" + db_path = STORAGE_FOLDER / "calibration" / "policy_data.db" return f"sqlite:///{db_path}" diff --git a/policyengine_us_data/utils/huggingface.py b/policyengine_us_data/utils/huggingface.py index a6c602e6..2860adf3 100644 --- a/policyengine_us_data/utils/huggingface.py +++ b/policyengine_us_data/utils/huggingface.py @@ -65,7 +65,6 @@ def download_calibration_inputs( paths = {} for key, hf_path in files.items(): - local_path = output_path / Path(hf_path).name hf_hub_download( repo_id=repo, filename=hf_path, @@ -74,6 +73,8 @@ def download_calibration_inputs( revision=version, token=TOKEN, ) + # hf_hub_download preserves directory structure + local_path = output_path / hf_path paths[key] = local_path print(f"Downloaded {hf_path} to {local_path}") From 5d6913a095f1d71764529753bc9c825b8dcef070 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 10 Dec 2025 10:23:26 -0500 Subject: [PATCH 10/24] documentation updates --- docs/local_area_calibration_setup.ipynb | 711 +++++++++++++++++++++++- 1 file changed, 681 insertions(+), 30 deletions(-) diff --git a/docs/local_area_calibration_setup.ipynb b/docs/local_area_calibration_setup.ipynb index e8eae899..c1ea246b 100644 --- a/docs/local_area_calibration_setup.ipynb +++ b/docs/local_area_calibration_setup.ipynb @@ -20,10 +20,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "cell-2", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/baogorek/envs/sep/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TEST_LITE == False\n" + ] + } + ], "source": [ "from sqlalchemy import create_engine, text\n", "import pandas as pd\n", @@ -45,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "cell-3", "metadata": {}, "outputs": [], @@ -73,10 +89,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "cell-5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Testing with 19 congressional districts:\n", + " NC (37): ['3701', '3702', '3703', '3704', '3705', '3706', '3707', '3708', '3709', '3710', '3711', '3712', '3713', '3714']\n", + " HI (15): ['1501', '1502']\n", + " MT (30): ['3001', '3002']\n", + " AK (2): ['201']\n" + ] + } + ], "source": [ "query = \"\"\"\n", "SELECT DISTINCT sc.value as cd_geoid\n", @@ -113,17 +141,29 @@ "\n", "The sparse matrix `X_sparse` has:\n", "- **Rows**: Calibration targets (e.g., SNAP totals by geography)\n", - "- **Columns**: (household \u00d7 CD) pairs - each household appears once per CD\n", + "- **Columns**: (household × CD) pairs - each household appears once per CD\n", "\n", "We filter to SNAP targets only (stratum_group_id=4) for this demonstration." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "cell-7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "X_sparse shape: (539, 256652)\n", + " Rows (targets): 539\n", + " Columns (household × CD pairs): 256652\n", + " Non-zero entries: 67,668\n", + " Sparsity: 99.95%\n" + ] + } + ], "source": [ "sim = Microsimulation(dataset=dataset_path)\n", "\n", @@ -140,7 +180,7 @@ "\n", "print(f\"X_sparse shape: {X_sparse.shape}\")\n", "print(f\" Rows (targets): {X_sparse.shape[0]}\")\n", - "print(f\" Columns (household \u00d7 CD pairs): {X_sparse.shape[1]}\")\n", + "print(f\" Columns (household × CD pairs): {X_sparse.shape[1]}\")\n", "print(f\" Non-zero entries: {X_sparse.nnz:,}\")\n", "print(f\" Sparsity: {1 - X_sparse.nnz / (X_sparse.shape[0] * X_sparse.shape[1]):.2%}\")" ] @@ -153,16 +193,91 @@ "## Section 4: Understanding the Matrix Structure with MatrixTracer\n", "\n", "The `MatrixTracer` helps navigate the sparse matrix by providing lookups between:\n", - "- Column indices \u2194 (household_id, CD) pairs\n", - "- Row indices \u2194 target definitions" + "- Column indices ↔ (household_id, CD) pairs\n", + "- Row indices ↔ target definitions" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "cell-9", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================================================================\n", + "MATRIX STRUCTURE BREAKDOWN\n", + "================================================================================\n", + "\n", + "Matrix dimensions: 539 rows x 256652 columns\n", + " Rows = 539 targets\n", + " Columns = 13508 households x 19 CDs\n", + " = 13,508 x 19 = 256,652\n", + "\n", + "--------------------------------------------------------------------------------\n", + "COLUMN STRUCTURE (Households stacked by CD)\n", + "--------------------------------------------------------------------------------\n", + "\n", + "Showing first and last 5 CDs of 19 total:\n", + "\n", + "First 5 CDs:\n", + "cd_geoid start_col end_col n_households\n", + " 1501 0 13507 13508\n", + " 1502 13508 27015 13508\n", + " 201 27016 40523 13508\n", + " 3001 40524 54031 13508\n", + " 3002 54032 67539 13508\n", + "\n", + "Last 5 CDs:\n", + "cd_geoid start_col end_col n_households\n", + " 3710 189112 202619 13508\n", + " 3711 202620 216127 13508\n", + " 3712 216128 229635 13508\n", + " 3713 229636 243143 13508\n", + " 3714 243144 256651 13508\n", + "\n", + "--------------------------------------------------------------------------------\n", + "ROW STRUCTURE (Targets)\n", + "--------------------------------------------------------------------------------\n", + "\n", + "Total targets: 539\n", + "\n", + "Targets by stratum group:\n", + " n_targets n_unique_vars\n", + "stratum_group_id \n", + "1 1 1\n", + "4 538 2\n", + "\n", + "--------------------------------------------------------------------------------\n", + "TARGET GROUPS (for loss calculation)\n", + "--------------------------------------------------------------------------------\n", + "\n", + "=== Creating Target Groups ===\n", + "\n", + "National targets:\n", + " Group 0: Snap = 107,062,860,000\n", + "\n", + "State targets:\n", + " Group 1: SNAP Household Count (51 targets)\n", + " Group 2: Snap (51 targets)\n", + "\n", + "District targets:\n", + " Group 3: SNAP Household Count (436 targets)\n", + "\n", + "Total groups created: 4\n", + "========================================\n", + " Group 0: National Snap (1 target, value=107,062,860,000) - rows [0]\n", + " Group 1: State SNAP Household Count (51 targets) - rows [1, 2, 3, ..., 50, 51]\n", + " Group 2: State Snap (51 targets) - rows [52, 53, 54, ..., 101, 102]\n", + " Group 3: District SNAP Household Count (436 targets) - rows [103, 104, 105, ..., 537, 538]\n", + "\n", + "================================================================================\n" + ] + } + ], "source": [ "tracer = MatrixTracer(\n", " targets_df, X_sparse, household_id_mapping, test_cds, sim\n", @@ -173,20 +288,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "cell-11", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=== Creating Target Groups ===\n", + "\n", + "National targets:\n", + " Group 0: Snap = 107,062,860,000\n", + "\n", + "State targets:\n", + " Group 1: SNAP Household Count (51 targets)\n", + " Group 2: Snap (51 targets)\n", + "\n", + "District targets:\n", + " Group 3: SNAP Household Count (436 targets)\n", + "\n", + "Total groups created: 4\n", + "========================================\n" + ] + } + ], "source": [ "target_groups, group_info = create_target_groups(targets_df)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "7e75756b-a317-4800-bac5-e0fd6bc43b8c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Row info for North Carolina's SNAP benefit amount:\n", + "{'row_index': 80, 'variable': 'snap', 'variable_desc': 'SNAP allotment', 'geographic_id': '37', 'target_value': 4041086120.0, 'stratum_id': 9799, 'stratum_group_id': 4}\n" + ] + } + ], "source": [ "target_group = tracer.get_group_rows(2)\n", "row_loc = target_group.iloc[28]['row_index'] # Manually found the index value 28\n", @@ -201,10 +347,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "c2be9721-ff11-4f78-ba0b-03407201dd53", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " household_id household_weight state_fips snap\n", + "0 25 1229.699951 23 789.199951\n", + "1 76 3119.330078 23 0.000000\n", + "2 92 1368.089966 23 0.000000\n", + "3 110 1457.579956 23 0.000000\n", + "4 140 1445.209961 23 0.000000\n", + "... ... ... ... ...\n", + "13503 178916 0.000000 15 0.000000\n", + "13504 178918 0.000000 15 0.000000\n", + "13505 178926 0.000000 15 0.000000\n", + "13506 178929 0.000000 15 0.000000\n", + "13507 178945 0.000000 15 0.000000\n", + "\n", + "[13508 rows x 4 columns]\n" + ] + } + ], "source": [ "hh_snap_df = pd.DataFrame(sim.calculate_dataframe([\n", " \"household_id\", \"household_weight\", \"state_fips\", \"snap\"]) \n", @@ -224,10 +391,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "cell-12", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " household_id household_weight state_fips snap\n", + "0 25 1229.699951 23 789.199951\n", + "\n", + "Evaluating the tracer.get_household_column_positions dictionary:\n", + "\n", + "{'1501': 0, '1502': 13508, '201': 27016, '3001': 40524, '3002': 54032, '3701': 67540, '3702': 81048, '3703': 94556, '3704': 108064, '3705': 121572, '3706': 135080, '3707': 148588, '3708': 162096, '3709': 175604, '3710': 189112, '3711': 202620, '3712': 216128, '3713': 229636, '3714': 243144}\n" + ] + } + ], "source": [ "# Reverse lookup: get all column positions for a specific household\n", "hh_id = hh_snap_df.loc[hh_snap_df.snap > 0].household_id.values[0]\n", @@ -248,10 +428,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "e05aaeab-3786-4ff0-a50b-34577065d2e0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Remember, this is a North Carolina target:\n", + "\n", + "target_id 9372\n", + "stratum_id 9799\n", + "variable snap\n", + "value 4041086120.0\n", + "period 2023\n", + "stratum_group_id 4\n", + "geographic_id 37\n", + "Name: 80, dtype: object\n", + "\n", + "Household donated to NC's 2nd district, 2023 SNAP dollars:\n", + "789.19995\n", + "\n", + "Household donated to NC's 2nd district, 2023 SNAP dollars:\n", + "0.0\n" + ] + } + ], "source": [ "print(\"Remember, this is a North Carolina target:\\n\")\n", "print(targets_df.iloc[row_loc])\n", @@ -277,10 +480,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "8cdc264c-8335-40eb-afd9-4c4d023ec303", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Row info for Alaska's SNAP benefit amount:\n", + "{'row_index': 80, 'variable': 'snap', 'variable_desc': 'SNAP allotment', 'geographic_id': '37', 'target_value': 4041086120.0, 'stratum_id': 9799, 'stratum_group_id': 4}\n" + ] + } + ], "source": [ "target_group = tracer.get_group_rows(2)\n", "new_row_loc = target_group.iloc[10]['row_index'] # Manually found the index value 10\n", @@ -295,10 +507,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "ac59b6f1-859f-4246-8a05-8cb26384c882", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Household donated to AK's 1st district, 2023 SNAP dollars:\n", + "342.48004\n" + ] + } + ], "source": [ "print(\"\\nHousehold donated to AK's 1st district, 2023 SNAP dollars:\")\n", "print(X_sparse[new_row_loc, positions['201']]) # Household donated to AK's at Large District" @@ -316,10 +538,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "cell-19", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SNAP values for first 5 households under different state rules:\n", + " NC rules: [789.19995117 0. 0. 0. 0. ]\n", + " AK rules: [342.4800415 0. 0. 0. 0. ]\n", + " Difference: [-446.71990967 0. 0. 0. 0. ]\n" + ] + } + ], "source": [ "def create_state_simulation(state_fips):\n", " \"\"\"Create a simulation with all households assigned to a specific state.\"\"\"\n", @@ -343,6 +576,424 @@ "print(f\" AK rules: {ak_snap}\")\n", "print(f\" Difference: {ak_snap - nc_snap}\")" ] + }, + { + "cell_type": "markdown", + "id": "a7a3b4f3-dabc-4160-a781-a529018e889f", + "metadata": {}, + "source": [ + "## Section 7: Creating the h5 files\n", + "\n", + " `w` (required)\n", + " - The calibrated weight vector from L0 calibration\n", + " - Shape: (n_cds * n_households,) — a flattened matrix where each CD has weights for all households\n", + " - Gets reshaped to (n_cds, n_households) internally\n", + "\n", + " `cds_to_calibrate` (required)\n", + " - The ordered list of CD GEOIDs used when building w\n", + " - Serves two purposes:\n", + " a. Tells us how to reshape w (via its length)\n", + " b. Provides the index mapping so we can extract the right rows for any cd_subset\n", + "\n", + " `cd_subset` (optional, default None)\n", + " - Which CDs to actually include in the output dataset\n", + " - Must be a subset of cds_to_calibrate\n", + " - If None, all CDs are included\n", + " - Use cases: build a single-state file, a single-CD file for testing, etc.\n", + "\n", + " `output_path` (optional but effectively required — raises if None)\n", + " - Where to save the resulting .h5 file\n", + " - Creates parent directories if needed\n", + "\n", + " `dataset_path` (optional, default None)\n", + " - Path to the base .h5 dataset that was used during calibration\n", + " - This is the \"template\" — household structure, demographics, etc.\n", + " - The function loads this, reweights households per CD, updates geography, and stacks" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "e1f8b237-ba42-4fca-8d43-f253f587d49b", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "from policyengine_us_data.datasets.cps.local_area_calibration.stacked_dataset_builder import create_sparse_cd_stacked_dataset\n", + "\n", + "# Initialize the weights w for demonstration\n", + "# We can't allow too many w cells to be positive for a given state, or the reindexing will fail\n", + "w = np.random.binomial(n=1, p=0.01, size=X_sparse.shape[1]).astype(float)\n", + "\n", + "# We'll make sure our earlier household is included:\n", + "household_ids = sim.calculate(\"household_id\", map_to=\"household\").values\n", + "hh_idx = np.where(household_ids == hh_id)[0][0]\n", + "\n", + "cd_idx = test_cds.index('3701')\n", + "flat_idx = cd_idx * len(household_ids) + hh_idx\n", + "w[flat_idx] = 2.5\n", + "\n", + "cd_idx = test_cds.index('201')\n", + "flat_idx = cd_idx * len(household_ids) + hh_idx\n", + "w[flat_idx] = 3.5\n", + "\n", + "# Create a folder for the outputs of the function that is to come.\n", + "new_folder_name = \"calibration_output\"\n", + "os.makedirs(new_folder_name, exist_ok=True)\n", + "output_path = os.path.join(new_folder_name, \"results.h5\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "650b807d-3d20-48e0-b512-43922ca2aace", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing subset of 2 CDs: 3701, 201...\n", + "Output path: calibration_output/results.h5\n", + "\n", + "Original dataset has 13,508 households\n", + "Extracted weights for 2 CDs from full weight matrix\n", + "Total active household-CD pairs: 278\n", + "Total weight in W matrix: 282\n", + "Processing CD 201 (2/2)...\n", + "\n", + "Combining 2 CD DataFrames...\n", + "Total households across all CDs: 278\n", + "Combined DataFrame shape: (799, 166)\n", + "\n", + "Reindexing all entity IDs using 25k ranges per CD...\n", + " Created 278 unique households across 2 CDs\n", + " Reindexing persons using 25k ranges...\n", + " Reindexing tax units...\n", + " Reindexing SPM units...\n", + " Reindexing marital units...\n", + " Final persons: 799\n", + " Final households: 278\n", + " Final tax units: 410\n", + " Final SPM units: 287\n", + " Final marital units: 622\n", + "\n", + "Weights in combined_df AFTER reindexing:\n", + " HH weight sum: 0.00M\n", + " Person weight sum: 0.00M\n", + " Ratio: 1.00\n", + "\n", + "Overflow check:\n", + " Max person ID after reindexing: 5,125,399\n", + " Max person ID × 100: 512,539,900\n", + " int32 max: 2,147,483,647\n", + " ✓ No overflow risk!\n", + "\n", + "Creating Dataset from combined DataFrame...\n", + "Building simulation from Dataset...\n", + "\n", + "Saving to calibration_output/results.h5...\n", + "Found 168 input variables (excluding calculated variables)\n", + "Variables saved: 163\n", + "Variables skipped: 3255\n", + "Sparse CD-stacked dataset saved successfully!\n", + "Household mapping saved to calibration_output/mappings/results_household_mapping.csv\n", + "\n", + "Verifying saved file...\n", + " Final households: 278\n", + " Final persons: 799\n", + " Total population (from household weights): 282\n", + " Total population (from person weights): 803\n", + " Average persons per household: 2.85\n" + ] + }, + { + "data": { + "text/plain": [ + "'calibration_output/results.h5'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cd_subset = ['3701', '201']\n", + "create_sparse_cd_stacked_dataset(\n", + " w,\n", + " test_cds, # cds_to_calibrate - Defines the structure of the weight vector w\n", + " cd_subset=cd_subset, # cd_subset - Specifies which CDs to actually include in the output dataset (optional, defaults to all).\n", + " dataset_path=dataset_path,\n", + " output_path=output_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f8d449b4-6069-44e0-8d21-e73944a1a1d2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0m\u001b[01;34mmappings\u001b[0m/ results.h5\n" + ] + } + ], + "source": [ + "%ls calibration_output" + ] + }, + { + "cell_type": "markdown", + "id": "04d7b733-bec5-49cb-9272-d167ae9c4693", + "metadata": {}, + "source": [ + "Note that there is a *mappings* directory that has also been created by create_sparse_cd_stacked_dataset. This contains the CSV file that links the original households to the donor households. The reason it's a seperate folder is to keep the h5 files and the mapping CSVs organized when this function is run for all districts or states." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "5fd7f7cc-6517-4f39-9a14-9cb147af38e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "results_household_mapping.csv\n" + ] + } + ], + "source": [ + "%ls calibration_output/mappings" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "578e8a69-b7ec-46bf-82ec-8020a46fd9cf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " household_id congressional_district_geoid \\\n", + "0 50000 201 \n", + "1 50001 201 \n", + "2 50002 201 \n", + "3 50003 201 \n", + "4 50004 201 \n", + ".. ... ... \n", + "273 125135 3701 \n", + "274 125136 3701 \n", + "275 125137 3701 \n", + "276 125138 3701 \n", + "277 125139 3701 \n", + "\n", + " county household_weight state_fips snap \n", + "0 ALEUTIANS_WEST_CENSUS_AREA_AK 3.5 2 342.480042 \n", + "1 BRISTOL_BAY_BOROUGH_AK 1.0 2 0.000000 \n", + "2 ALEUTIANS_EAST_BOROUGH_AK 1.0 2 0.000000 \n", + "3 NOME_CENSUS_AREA_AK 1.0 2 0.000000 \n", + "4 WRANGELL_CITY_AND_BOROUGH_AK 1.0 2 0.000000 \n", + ".. ... ... ... ... \n", + "273 WASHINGTON_COUNTY_NC 1.0 37 0.000000 \n", + "274 CURRITUCK_COUNTY_NC 1.0 37 0.000000 \n", + "275 LENOIR_COUNTY_NC 1.0 37 0.000000 \n", + "276 WAYNE_COUNTY_NC 1.0 37 0.000000 \n", + "277 NASH_COUNTY_NC 1.0 37 0.000000 \n", + "\n", + "[278 rows x 6 columns]\n" + ] + } + ], + "source": [ + "sim_after = Microsimulation(dataset=\"./calibration_output/results.h5\")\n", + "\n", + "hh_after_df = pd.DataFrame(sim_after.calculate_dataframe([\n", + " \"household_id\", \"congressional_district_geoid\", \"county\", \"household_weight\", \"state_fips\", \"snap\"]) \n", + ")\n", + "print(hh_after_df)" + ] + }, + { + "cell_type": "markdown", + "id": "83769d86-91e1-41bb-b718-01ee09cc7e2a", + "metadata": {}, + "source": [ + "We can see one of the correct instances above but let's confirm that this new household id does in fact link back to the original in both cases." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "27baf521-1bd6-4ef0-9f70-4381fd842b52", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
new_household_idoriginal_household_idcongressional_districtstate_fips
050000252012
112500025370137
\n", + "
" + ], + "text/plain": [ + " new_household_id original_household_id congressional_district state_fips\n", + "0 50000 25 201 2\n", + "1 125000 25 3701 37" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mapping_df = pd.read_csv(\"calibration_output/mappings/results_household_mapping.csv\")\n", + "mapping_df.loc[mapping_df.original_household_id == hh_id]" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "36be0858-33f4-4c65-a74f-e18a76ce8eea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
household_idcongressional_district_geoidcountyhousehold_weightstate_fipssnap
050000201ALEUTIANS_WEST_CENSUS_AREA_AK3.52342.480042
1381250003701VANCE_COUNTY_NC2.537789.199951
\n", + "
" + ], + "text/plain": [ + " household_id congressional_district_geoid \\\n", + "0 50000 201 \n", + "138 125000 3701 \n", + "\n", + " county household_weight state_fips snap \n", + "0 ALEUTIANS_WEST_CENSUS_AREA_AK 3.5 2 342.480042 \n", + "138 VANCE_COUNTY_NC 2.5 37 789.199951 " + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_hh_ids = mapping_df.loc[mapping_df.original_household_id == hh_id].new_household_id\n", + "hh_after_df.loc[hh_after_df.household_id.isin(new_hh_ids)]" + ] + }, + { + "cell_type": "markdown", + "id": "96fa8407-008f-4eaa-8f22-a803b72e71e4", + "metadata": {}, + "source": [ + "And we can see that the snap numbers still match their values from the different US state systems. However note that due to the use of policyengine-core's random function in a component of snap_gross_income, for some households, the value in the final simulation will not match the one used in creating the X matrix (`X_sparse` here). This is outlined in [Issue 412](https://github.com/PolicyEngine/policyengine-core/issues/412)." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "90ee3a8b-d529-41f2-83ee-d543c53b5492", + "metadata": {}, + "outputs": [], + "source": [ + "%rm -r calibration_output" + ] } ], "metadata": { @@ -366,4 +1017,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From fbb64364056c1f440f68b2f14dc67429ac275565 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 10 Dec 2025 12:40:41 -0500 Subject: [PATCH 11/24] Add deterministic test fixture and tests for stacked_dataset_builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a minimal 50-household H5 fixture with known values for stable testing of the stacked dataset builder without relying on sampled stratified CPS data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 4 + .../create_test_fixture.py | 178 ++++++++++++++++++ .../test_fixture_50hh.h5 | Bin 0 -> 38000 bytes .../test_stacked_dataset_builder.py | 158 ++++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5 create mode 100644 policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py diff --git a/.gitignore b/.gitignore index 36301c6f..3d85ac86 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ **/__pycache__ **/.DS_STORE **/*.h5 +**/*.npy **/*.csv **/_build **/*.pkl @@ -24,3 +25,6 @@ node_modules !policyengine_us_data/storage/social_security_aux.csv !policyengine_us_data/storage/SSPopJul_TR2024.csv docs/.ipynb_checkpoints/ + +## Test fixtures +!policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5 diff --git a/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py b/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py new file mode 100644 index 00000000..458dab13 --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py @@ -0,0 +1,178 @@ +""" +Create a minimal deterministic H5 test fixture for stacked_dataset_builder tests. + +This creates a tiny dataset (~50 households) with known, fixed values that +won't change between test runs. The fixture is used to test the H5 creation +pipeline without depending on the full stratified CPS. + +Run this script to regenerate the fixture: + python create_test_fixture.py +""" + +import numpy as np +import h5py +from pathlib import Path + +FIXTURE_PATH = Path(__file__).parent / "test_fixture_50hh.h5" +TIME_PERIOD = "2023" +N_HOUSEHOLDS = 50 +SEED = 42 + + +def create_test_fixture(): + np.random.seed(SEED) + + # Create household structure: 50 households with 1-4 persons each + persons_per_hh = np.random.choice([1, 2, 3, 4], size=N_HOUSEHOLDS, p=[0.3, 0.4, 0.2, 0.1]) + n_persons = persons_per_hh.sum() + + # Household-level arrays + household_ids = np.arange(N_HOUSEHOLDS, dtype=np.int32) + household_weights = np.random.uniform(500, 3000, N_HOUSEHOLDS).astype(np.float32) + + # Assign households to states (use NC=37 and AK=2 for testing) + # 40 households in NC, 10 in AK + state_fips_hh = np.array([37] * 40 + [2] * 10, dtype=np.int32) + + # SNAP values: some households get SNAP, values differ by state + # NC SNAP ~$200-800, AK SNAP ~$300-900 (higher cost of living) + snap_hh = np.zeros(N_HOUSEHOLDS, dtype=np.float32) + snap_recipients = np.random.choice(N_HOUSEHOLDS, size=15, replace=False) + for hh_idx in snap_recipients: + if state_fips_hh[hh_idx] == 37: # NC + snap_hh[hh_idx] = np.random.uniform(200, 800) + else: # AK + snap_hh[hh_idx] = np.random.uniform(300, 900) + + # Person-level arrays + person_ids = np.arange(n_persons, dtype=np.int32) + person_household_ids = np.repeat(household_ids, persons_per_hh) + person_weights = np.repeat(household_weights, persons_per_hh) + + # Age: mix of ages + ages = np.random.choice( + [5, 15, 25, 35, 45, 55, 65, 75], + size=n_persons, + p=[0.1, 0.1, 0.15, 0.2, 0.15, 0.15, 0.1, 0.05] + ).astype(np.int32) + + # Employment income: working-age adults have income + employment_income = np.zeros(n_persons, dtype=np.float32) + working_age_mask = (ages >= 18) & (ages < 65) + employment_income[working_age_mask] = np.random.uniform( + 0, 80000, working_age_mask.sum() + ) + + # Tax unit structure: simplify - each household is one tax unit + tax_unit_ids = np.arange(N_HOUSEHOLDS, dtype=np.int32) + person_tax_unit_ids = np.repeat(tax_unit_ids, persons_per_hh) + tax_unit_weights = household_weights.copy() + + # SPM unit: same as household for simplicity + spm_unit_ids = np.arange(N_HOUSEHOLDS, dtype=np.int32) + person_spm_unit_ids = np.repeat(spm_unit_ids, persons_per_hh) + spm_unit_weights = household_weights.copy() + + # Family: same as household + family_ids = np.arange(N_HOUSEHOLDS, dtype=np.int32) + person_family_ids = np.repeat(family_ids, persons_per_hh) + family_weights = household_weights.copy() + + # Marital unit: each person is their own marital unit for simplicity + marital_unit_ids = np.arange(n_persons, dtype=np.int32) + person_marital_unit_ids = marital_unit_ids.copy() + marital_unit_weights = person_weights.copy() + + # SNAP is at SPM unit level (same as household in our simple structure) + snap_spm = snap_hh.copy() + # state_fips is at household level + + # Write H5 file + print(f"Creating test fixture: {FIXTURE_PATH}") + print(f" Households: {N_HOUSEHOLDS}") + print(f" Persons: {n_persons}") + + with h5py.File(FIXTURE_PATH, "w") as f: + # Household variables + f.create_group("household_id") + f["household_id"].create_dataset(TIME_PERIOD, data=household_ids) + + f.create_group("household_weight") + f["household_weight"].create_dataset(TIME_PERIOD, data=household_weights) + + # Person variables + f.create_group("person_id") + f["person_id"].create_dataset(TIME_PERIOD, data=person_ids) + + f.create_group("person_household_id") + f["person_household_id"].create_dataset(TIME_PERIOD, data=person_household_ids) + + f.create_group("person_weight") + f["person_weight"].create_dataset(TIME_PERIOD, data=person_weights) + + f.create_group("age") + f["age"].create_dataset(TIME_PERIOD, data=ages) + + f.create_group("employment_income") + f["employment_income"].create_dataset(TIME_PERIOD, data=employment_income) + + # Tax unit + f.create_group("tax_unit_id") + f["tax_unit_id"].create_dataset(TIME_PERIOD, data=tax_unit_ids) + + f.create_group("person_tax_unit_id") + f["person_tax_unit_id"].create_dataset(TIME_PERIOD, data=person_tax_unit_ids) + + f.create_group("tax_unit_weight") + f["tax_unit_weight"].create_dataset(TIME_PERIOD, data=tax_unit_weights) + + # SPM unit + f.create_group("spm_unit_id") + f["spm_unit_id"].create_dataset(TIME_PERIOD, data=spm_unit_ids) + + f.create_group("person_spm_unit_id") + f["person_spm_unit_id"].create_dataset(TIME_PERIOD, data=person_spm_unit_ids) + + f.create_group("spm_unit_weight") + f["spm_unit_weight"].create_dataset(TIME_PERIOD, data=spm_unit_weights) + + # Family + f.create_group("family_id") + f["family_id"].create_dataset(TIME_PERIOD, data=family_ids) + + f.create_group("person_family_id") + f["person_family_id"].create_dataset(TIME_PERIOD, data=person_family_ids) + + f.create_group("family_weight") + f["family_weight"].create_dataset(TIME_PERIOD, data=family_weights) + + # Marital unit + f.create_group("marital_unit_id") + f["marital_unit_id"].create_dataset(TIME_PERIOD, data=marital_unit_ids) + + f.create_group("person_marital_unit_id") + f["person_marital_unit_id"].create_dataset(TIME_PERIOD, data=person_marital_unit_ids) + + f.create_group("marital_unit_weight") + f["marital_unit_weight"].create_dataset(TIME_PERIOD, data=marital_unit_weights) + + # Geography (household level) + f.create_group("state_fips") + f["state_fips"].create_dataset(TIME_PERIOD, data=state_fips_hh) + + # SNAP (at SPM unit level) + f.create_group("snap") + f["snap"].create_dataset(TIME_PERIOD, data=snap_spm) + + print("Done!") + + # Verify + with h5py.File(FIXTURE_PATH, "r") as f: + print(f"\nVerification:") + print(f" Variables: {list(f.keys())}") + print(f" household_id shape: {f['household_id'][TIME_PERIOD].shape}") + print(f" person_id shape: {f['person_id'][TIME_PERIOD].shape}") + + +if __name__ == "__main__": + create_test_fixture() diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5 b/policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5 new file mode 100644 index 0000000000000000000000000000000000000000..f169cb9e6985aa23f9d8993c142317cebf1abf11 GIT binary patch literal 38000 zcmeHQ3v3m~6<~UCd}2Zg#!zBt6CU=i*FXS=Ukub36AX5O?U&dDgCH>o zh|@}q3y4}#D`=CpBvpw+D?U|etTw9LMpfKKX=$ZKMwEs|AX!lok*HOB&(7S(zJX;~ z>)C$Ko@D-@pYS9Z_%n=j8J{ZiSJSOrZdr!W-{Ab&m6sXq1BH+CDF}&hlT6UOT6mgeSUbH- z0|nNCLe!{5K35I$feY^t_4Inq8mEZ!&6&4OS3gahKQ>*seDunvKx?e-B3@bg&qbFLxJ1)bmRU)44B1760=cn2k^M8G+ zlAjE>ju7#&J|-Ch#cv`DmLyxWq$ClMEpNzfe`@D&CWR_Cz&V%U90wZ7PJF+9{hSMv zWj_6r>W4T9j8@@9j*SstJbA=(l|;1>k~uh@N|#t=!Gi#E!=sO91(Yy|0F^?&cregG z8%h{P7*4p8a2EkQf=B;u0v`Q)2qOuj2x$b^%e{org!>3%2xAH32;&JEgb9TE2@?sE z2$KnDHXk5l5~dO!B%q1>3IR>zLxdTGEW%8}lW)(l3X9XN&xU4O>8aB!qv#9s+M*fO zAD7NHb9Zky*PHj6l@GpV&iMSad1dh-v;JI$W&Y=9=5H4rF`Lsao9!c3nHRo0#v0u; z&D>ct*ZRZ1)2zMOJIrs^80NM&##xI`4!0I&rdn;kNwX%~)6CrcC(PkbTjs?46 z8rJ!beq^pbJIOq9;;8w?xYx}2p;T+hhyAR*?NhDn)IXc|yltEBzICUyW&9{>`oFH4 z&8O0=AHR3rJofhsX2E^$n0H+qX8xcmWKvaxeUh8VzXx~-4asc6R|#_n4-;|-a|!bZ zUn6{-@Ce~i!hAxAuz-+D$Rj`@79pQt6AB0m35y7e36Bw$5S9{_5ef;96P6QJ5LOaa z5zsw1?3)2QznQJW-r45a?qU0G`v(Uwzy%DI6POp68{GV$as=}Pa|QUq z%^A!aZthU|102Etm$(65VSr!U9K$^0<{CHOfO8nkJw{!ROH?Ak63CM;gut=lQw?3bUR8ed*k#zV0lUj`o+r8{kUYR34;xF{{zNP9v4 zWZvVW0(5~%T7?riHbi{!;8Dj#A#@b91lE8TFScM{L(-0p7KtA(mSf;UqIps1p|2gi zc+u$;xRlY|yjUvl&2I4dqLiT`i+xG1YLK@vIv6J*l^2V;qjMD}b&q8+ym;|E2ACw* zFu6nG$BXrt{2|f2DD=?Rj$XW&j!6lZBpxpw#&ixzV)9~G(Z8sRKKg0Eix)dG#FZ%+ z+>`k6V!>pQPV=JBLti_3@nV*Iepnx|=S4ihH?EV}fQfdJW_f?F^0jqw`9vwMAN|Jt z+^=P~KfQ@U{eqAC)A6c{MCJ4N9{1;9f?i8t4fyeM4qil(Xnq!Y=xaw${>zNkMC|$b z*6Tl*=hzKCp8pt>3P2E zZ2vBdB?lxb@5OijE*%Rev~)@XufExlU-#dA_WlvS%Rljt75Sr$hwPd82knh}*VvaP zW!s;1d}1Nv!&xEwfs?Ob*^wg`N6*MF`Er62zB1}1d-v`wcJ80nIN@I&SZ5zO(T-(G zj+}h{bi|K{e_q*T-#vA=eQD>5_R+V#ZEt#ayuI}7m`L78^Zo5R?6G4P+iQ#0*-yP$ z7diKE%D3!!^WTrkFaO-fk?=F0e(aPNc~qR5ZI3xM%lhjJsrJD`Q|(tq{XGBmALiK0 z>(i|h=jK_18tUym<1XYEzdtVjxmgpPd+a(Iwu>(w$baM~xlXur{Rd8dOJ?t}N`_6b z-}@xf-jr?I%RbuZoFBQf$sSxJ^f2UazmJpy_lI11{(9M`TCYc)KRtbjlLy9PSWDHP zFn}x(+O z>x~yI3|=F4GhMJgSSNjLotR$tsck~#2W=C*V=XjMU`)kpp1uhSlS$5CVKRx|xG02$ z%OrYSB=peNj$Y%U^YZy&eRTJ@2;|6)`gEqqp;b7MV`Ib@Pabt$M;0~}XbG$V zFJ5fM<_wZHY|bF@!M6 zr&HIJhUAIn=fnk^?z{|Ns}u2UGQaanqtiK?p(rZjaS`7n(=|wY=KNIq4*p&>=swe5 z$)-u2Z%d5!rE!5&q;>*nz^i>pF-106a+b)ieMw&^((CpmkotQ~Vz2h4Flv^O`oA&$ zjzQP+qOnQ*NQuFVUiPfEpI+(8_lnv!>=Uk^etA5@Zs*tYgs>%=EWi0C*us^gmE zf;cHyf)&HBJ!xDbDx}+!1p8rQRNUaa6STjZjL#pCsC<(c{oVS z$4|$Xi~DMR3a0}k8xp ztA*>wi^*$5I?anh4}I^ma9ML0%-+(*Otsrzp(%XA(1xD^SfQ|%`#PqZuZ+v>QxL1bfu<@;~WPkkTW zEYn@i6CO^d&fm#ozE1FdcW2jkuEZyJNLqyxIdaV-5k2Zr+m~xk^k{Js_U2ygi~3zq zzxE{qAA}=G7LGplF%5XNFK6(yjvc-@Xq04ye8Dj2Uz zu+(H3p^$)S5KK{GID#SkDguUJ=)M*aFic-dSVut93HnCY3XRYP0;D(aQX>BbuxGYu z{?0D%snFlr72opi>Tm4|OX#|z*SJVs$L2RK%6VFpQICtd?zf8PoV~_HZ57d_?(T6> zsk}YA!PmWKZ4g-`=KF}ur=s<*L${{^FJ4stKa3wQ7KX+BG%wzs-g{drFJ8P{6b@*LUd-Ah?x1`e1yuGYqOL>GH!e!6aR9DGFbg>LyPcAO! z_{hbjTgseUl~snfR_~}RtEw%jsCu%xQp6fYZE0P}wyFw>b#BkstI^lf$((D#l{dRi zjW!|?^198Xl@(ie@ZBQyP@@eQ5vfOI>9ZBJrCTHSl=WAmji`vo)rL`1TUuLIvbiE$ zBQ#%ARqBisljIMP!yXyd&aE||_V14?`@U{{wLZ%l@N1tk_K1|aePWIEE!V4kIwSYR zvo>PgKKZSG;r=?iouALxCz~R*{~q7-87cLwdF=+#fFIwb91yu_z6+w&-jmjg?~d<_ zu48xe9nagc8+@Lk=tWV3*64~P$p7gw_AABK!Q0ZnWJ(p!z&ga+fm+#$N9JLfk|SSy z@VT=a;V%=`%UuZ~zBcdIPPnD!Y@Ss;+?;rEbKcqyf9k|lzmpfaUS{JsSGT_$m52M> zm&Lg^y=GX`>AVXK7xLOSkGkP}(UFli#JTg-eystm0j&Y80j+_q(16!_3U%M1-?%9A zkSLuV7j?z26#sd8jf-03zE!S4&yR~byFWBTPWY(fBN@#V>|s}(_f9|9!v#vv+y5U% C$f;BS literal 0 HcmV?d00001 diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py b/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py new file mode 100644 index 00000000..9f9729b4 --- /dev/null +++ b/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py @@ -0,0 +1,158 @@ +"""Tests for stacked_dataset_builder.py using deterministic test fixture.""" + +import os +import tempfile +import numpy as np +import pandas as pd +import pytest + +from policyengine_us import Microsimulation +from policyengine_us_data.datasets.cps.local_area_calibration.stacked_dataset_builder import ( + create_sparse_cd_stacked_dataset, +) + +FIXTURE_PATH = ( + "policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5" +) +TEST_CDS = ["3701", "201"] # NC-01 and AK at-large +SEED = 42 + + +@pytest.fixture(scope="module") +def fixture_sim(): + return Microsimulation(dataset=FIXTURE_PATH) + + +@pytest.fixture(scope="module") +def n_households(fixture_sim): + return fixture_sim.calculate("household_id", map_to="household").shape[0] + + +@pytest.fixture(scope="module") +def test_weights(n_households): + """Create deterministic weight vector with known households.""" + np.random.seed(SEED) + n_cds = len(TEST_CDS) + w = np.zeros(n_households * n_cds, dtype=float) + + # Give 5 households in each CD a weight + for cd_idx in range(n_cds): + hh_indices = np.random.choice(n_households, size=5, replace=False) + for hh_idx in hh_indices: + w[cd_idx * n_households + hh_idx] = np.random.uniform(1, 3) + + return w + + +@pytest.fixture(scope="module") +def stacked_result(test_weights): + """Run stacked dataset builder and return results.""" + with tempfile.TemporaryDirectory() as tmpdir: + output_path = os.path.join(tmpdir, "test_output.h5") + + create_sparse_cd_stacked_dataset( + test_weights, + TEST_CDS, + cd_subset=TEST_CDS, + dataset_path=FIXTURE_PATH, + output_path=output_path, + ) + + sim_after = Microsimulation(dataset=output_path) + hh_df = pd.DataFrame( + sim_after.calculate_dataframe( + [ + "household_id", + "congressional_district_geoid", + "county", + "household_weight", + "state_fips", + ] + ) + ) + + mapping_path = os.path.join( + tmpdir, "mappings", "test_output_household_mapping.csv" + ) + mapping_df = pd.read_csv(mapping_path) + + yield {"hh_df": hh_df, "mapping_df": mapping_df} + + +class TestStackedDatasetBuilder: + def test_output_has_correct_cd_count(self, stacked_result): + """Output should contain households from both CDs.""" + hh_df = stacked_result["hh_df"] + cds_in_output = hh_df["congressional_district_geoid"].unique() + assert len(cds_in_output) == len(TEST_CDS) + + def test_output_contains_both_cds(self, stacked_result): + """Output should contain both NC-01 (3701) and AK-AL (201).""" + hh_df = stacked_result["hh_df"] + cds_in_output = set(hh_df["congressional_district_geoid"].unique()) + expected = {3701, 201} + assert cds_in_output == expected + + def test_state_fips_matches_cd(self, stacked_result): + """State FIPS should match the CD's state.""" + hh_df = stacked_result["hh_df"] + + for _, row in hh_df.iterrows(): + cd_geoid = row["congressional_district_geoid"] + state_fips = row["state_fips"] + expected_state = cd_geoid // 100 + assert state_fips == expected_state + + def test_household_ids_are_unique(self, stacked_result): + """Each household should have a unique ID.""" + hh_df = stacked_result["hh_df"] + assert hh_df["household_id"].nunique() == len(hh_df) + + def test_mapping_has_required_columns(self, stacked_result): + """Mapping CSV should have expected columns.""" + mapping_df = stacked_result["mapping_df"] + required_cols = [ + "new_household_id", + "original_household_id", + "congressional_district", + "state_fips", + ] + for col in required_cols: + assert col in mapping_df.columns + + def test_mapping_covers_all_output_households(self, stacked_result): + """Every output household should be in the mapping.""" + hh_df = stacked_result["hh_df"] + mapping_df = stacked_result["mapping_df"] + + output_hh_ids = set(hh_df["household_id"].values) + mapped_hh_ids = set(mapping_df["new_household_id"].values) + assert output_hh_ids == mapped_hh_ids + + def test_weights_are_positive(self, stacked_result): + """All household weights should be positive.""" + hh_df = stacked_result["hh_df"] + assert (hh_df["household_weight"] > 0).all() + + def test_counties_match_state(self, stacked_result): + """County names should end with correct state code.""" + hh_df = stacked_result["hh_df"] + + for _, row in hh_df.iterrows(): + county = row["county"] + state_fips = row["state_fips"] + + if state_fips == 37: + assert county.endswith( + "_NC" + ), f"NC county should end with _NC: {county}" + elif state_fips == 2: + assert county.endswith( + "_AK" + ), f"AK county should end with _AK: {county}" + + def test_household_count_matches_weights(self, stacked_result, test_weights): + """Number of output households should match non-zero weights.""" + hh_df = stacked_result["hh_df"] + expected_households = (test_weights > 0).sum() + assert len(hh_df) == expected_households From 5b7fbb8040f5b0f864ff187cf27d54326aaae707 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 10 Dec 2025 12:49:41 -0500 Subject: [PATCH 12/24] Fix dtype warning in stacked_dataset_builder person ID assignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cast np.arange output to int32 to match column dtype. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../cps/local_area_calibration/stacked_dataset_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py index ec21a908..47534a29 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -447,7 +447,9 @@ def create_sparse_cd_stacked_dataset( ) # Create sequential IDs for this CD - new_person_ids = np.arange(start_id, start_id + n_persons_in_cd) + new_person_ids = np.arange( + start_id, start_id + n_persons_in_cd, dtype=np.int32 + ) # Assign all at once using loc combined_df.loc[cd_mask, person_id_col] = new_person_ids From 18d635a3289e1aa7ba6973f1a80eec7f83c2fee2 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 10 Dec 2025 12:51:01 -0500 Subject: [PATCH 13/24] Format test files with black MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../create_test_fixture.py | 42 ++++++++++++++----- .../test_stacked_dataset_builder.py | 8 ++-- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py b/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py index 458dab13..00334734 100644 --- a/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py +++ b/policyengine_us_data/tests/test_local_area_calibration/create_test_fixture.py @@ -23,12 +23,16 @@ def create_test_fixture(): np.random.seed(SEED) # Create household structure: 50 households with 1-4 persons each - persons_per_hh = np.random.choice([1, 2, 3, 4], size=N_HOUSEHOLDS, p=[0.3, 0.4, 0.2, 0.1]) + persons_per_hh = np.random.choice( + [1, 2, 3, 4], size=N_HOUSEHOLDS, p=[0.3, 0.4, 0.2, 0.1] + ) n_persons = persons_per_hh.sum() # Household-level arrays household_ids = np.arange(N_HOUSEHOLDS, dtype=np.int32) - household_weights = np.random.uniform(500, 3000, N_HOUSEHOLDS).astype(np.float32) + household_weights = np.random.uniform(500, 3000, N_HOUSEHOLDS).astype( + np.float32 + ) # Assign households to states (use NC=37 and AK=2 for testing) # 40 households in NC, 10 in AK @@ -53,7 +57,7 @@ def create_test_fixture(): ages = np.random.choice( [5, 15, 25, 35, 45, 55, 65, 75], size=n_persons, - p=[0.1, 0.1, 0.15, 0.2, 0.15, 0.15, 0.1, 0.05] + p=[0.1, 0.1, 0.15, 0.2, 0.15, 0.15, 0.1, 0.05], ).astype(np.int32) # Employment income: working-age adults have income @@ -98,14 +102,18 @@ def create_test_fixture(): f["household_id"].create_dataset(TIME_PERIOD, data=household_ids) f.create_group("household_weight") - f["household_weight"].create_dataset(TIME_PERIOD, data=household_weights) + f["household_weight"].create_dataset( + TIME_PERIOD, data=household_weights + ) # Person variables f.create_group("person_id") f["person_id"].create_dataset(TIME_PERIOD, data=person_ids) f.create_group("person_household_id") - f["person_household_id"].create_dataset(TIME_PERIOD, data=person_household_ids) + f["person_household_id"].create_dataset( + TIME_PERIOD, data=person_household_ids + ) f.create_group("person_weight") f["person_weight"].create_dataset(TIME_PERIOD, data=person_weights) @@ -114,14 +122,18 @@ def create_test_fixture(): f["age"].create_dataset(TIME_PERIOD, data=ages) f.create_group("employment_income") - f["employment_income"].create_dataset(TIME_PERIOD, data=employment_income) + f["employment_income"].create_dataset( + TIME_PERIOD, data=employment_income + ) # Tax unit f.create_group("tax_unit_id") f["tax_unit_id"].create_dataset(TIME_PERIOD, data=tax_unit_ids) f.create_group("person_tax_unit_id") - f["person_tax_unit_id"].create_dataset(TIME_PERIOD, data=person_tax_unit_ids) + f["person_tax_unit_id"].create_dataset( + TIME_PERIOD, data=person_tax_unit_ids + ) f.create_group("tax_unit_weight") f["tax_unit_weight"].create_dataset(TIME_PERIOD, data=tax_unit_weights) @@ -131,7 +143,9 @@ def create_test_fixture(): f["spm_unit_id"].create_dataset(TIME_PERIOD, data=spm_unit_ids) f.create_group("person_spm_unit_id") - f["person_spm_unit_id"].create_dataset(TIME_PERIOD, data=person_spm_unit_ids) + f["person_spm_unit_id"].create_dataset( + TIME_PERIOD, data=person_spm_unit_ids + ) f.create_group("spm_unit_weight") f["spm_unit_weight"].create_dataset(TIME_PERIOD, data=spm_unit_weights) @@ -141,7 +155,9 @@ def create_test_fixture(): f["family_id"].create_dataset(TIME_PERIOD, data=family_ids) f.create_group("person_family_id") - f["person_family_id"].create_dataset(TIME_PERIOD, data=person_family_ids) + f["person_family_id"].create_dataset( + TIME_PERIOD, data=person_family_ids + ) f.create_group("family_weight") f["family_weight"].create_dataset(TIME_PERIOD, data=family_weights) @@ -151,10 +167,14 @@ def create_test_fixture(): f["marital_unit_id"].create_dataset(TIME_PERIOD, data=marital_unit_ids) f.create_group("person_marital_unit_id") - f["person_marital_unit_id"].create_dataset(TIME_PERIOD, data=person_marital_unit_ids) + f["person_marital_unit_id"].create_dataset( + TIME_PERIOD, data=person_marital_unit_ids + ) f.create_group("marital_unit_weight") - f["marital_unit_weight"].create_dataset(TIME_PERIOD, data=marital_unit_weights) + f["marital_unit_weight"].create_dataset( + TIME_PERIOD, data=marital_unit_weights + ) # Geography (household level) f.create_group("state_fips") diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py b/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py index 9f9729b4..d977392f 100644 --- a/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py +++ b/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py @@ -11,9 +11,7 @@ create_sparse_cd_stacked_dataset, ) -FIXTURE_PATH = ( - "policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5" -) +FIXTURE_PATH = "policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5" TEST_CDS = ["3701", "201"] # NC-01 and AK at-large SEED = 42 @@ -151,7 +149,9 @@ def test_counties_match_state(self, stacked_result): "_AK" ), f"AK county should end with _AK: {county}" - def test_household_count_matches_weights(self, stacked_result, test_weights): + def test_household_count_matches_weights( + self, stacked_result, test_weights + ): """Number of output households should match non-zero weights.""" hh_df = stacked_result["hh_df"] expected_households = (test_weights > 0).sum() From febbd6813d70ad05563bfd306fce276967e07822 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Fri, 12 Dec 2025 11:32:08 -0500 Subject: [PATCH 14/24] Add spm_unit_tenure_type and fix input_variables detection in stratification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add spm_unit_tenure_type mapping from SPM_TENMORTSTATUS in add_spm_variables - Fix create_stratified_cps.py to use source sim's input_variables instead of empty sim - Fix stacked_dataset_builder.py to use base_sim's input_variables instead of sparse_sim The input_variables fix ensures variables like spm_unit_tenure_type are preserved when creating stratified/stacked datasets, since input_variables is only populated from variables that have actual data in the loaded dataset. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- policyengine_us_data/datasets/cps/cps.py | 12 ++++++++++++ .../local_area_calibration/create_stratified_cps.py | 2 +- .../stacked_dataset_builder.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/policyengine_us_data/datasets/cps/cps.py b/policyengine_us_data/datasets/cps/cps.py index 06619d6e..329a87e7 100644 --- a/policyengine_us_data/datasets/cps/cps.py +++ b/policyengine_us_data/datasets/cps/cps.py @@ -625,6 +625,18 @@ def add_spm_variables(cps: h5py.File, spm_unit: DataFrame) -> None: if asec_variable in spm_unit.columns: cps[openfisca_variable] = spm_unit[asec_variable] + if "SPM_TENMORTSTATUS" in spm_unit.columns: + tenure_map = { + 1: "OWNER_WITH_MORTGAGE", + 2: "OWNER_WITHOUT_MORTGAGE", + 3: "RENTER", + } + cps["spm_unit_tenure_type"] = ( + spm_unit.SPM_TENMORTSTATUS.map(tenure_map) + .fillna("RENTER") + .astype("S") + ) + cps["reduced_price_school_meals_reported"] = ( cps["free_school_meals_reported"] * 0 ) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py index 8dccf34a..3b3c51cd 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py @@ -201,7 +201,7 @@ def create_stratified_cps_dataset( data = {} # Only save input variables (not calculated/derived variables) - input_vars = set(stratified_sim.input_variables) + input_vars = set(sim.input_variables) print( f"Found {len(input_vars)} input variables (excluding calculated variables)" ) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py index 47534a29..1dc7f7be 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -566,7 +566,7 @@ def create_sparse_cd_stacked_dataset( # Only save input variables (not calculated/derived variables) # Calculated variables like state_name, state_code will be recalculated on load - input_vars = set(sparse_sim.input_variables) + input_vars = set(base_sim.input_variables) print( f"Found {len(input_vars)} input variables (excluding calculated variables)" ) From 8185b575e1fff6ebb96510be38415ab0682bd57b Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Mon, 15 Dec 2025 22:18:23 -0500 Subject: [PATCH 15/24] Add real rent-based SPM geographic adjustments to stacked dataset builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add spm-calculator integration for SPM threshold calculation - Replace random placeholder geoadj with real values from Census ACS rent data - Add load_cd_geoadj_values() to compute geoadj from median 2BR rents - Add calculate_spm_thresholds_for_cd() to calculate SPM thresholds per CD - Add CD rent data CSV and fetch script (requires CENSUS_API_KEY) - Update .gitignore to track rent CSV 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 1 + .../calibration_utils.py | 109 ++++- .../stacked_dataset_builder.py | 15 + policyengine_us_data/storage/README.md | 7 +- .../storage/fetch_cd_rents.py | 54 +++ .../national_and_district_rents_2023.csv | 440 ++++++++++++++++++ 6 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 policyengine_us_data/storage/fetch_cd_rents.py create mode 100644 policyengine_us_data/storage/national_and_district_rents_2023.csv diff --git a/.gitignore b/.gitignore index 3d85ac86..64914192 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ node_modules !soi_targets.csv !policyengine_us_data/storage/social_security_aux.csv !policyengine_us_data/storage/SSPopJul_TR2024.csv +!policyengine_us_data/storage/national_and_district_rents_2023.csv docs/.ipynb_checkpoints/ ## Test fixtures diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index e7f41f85..e5058133 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -2,10 +2,14 @@ Shared utilities for calibration scripts. """ -from typing import List, Tuple +from typing import Dict, List, Tuple import numpy as np import pandas as pd +# TODO: Add spm-calculator to PyPI dependencies when available +from spm_calculator import SPMCalculator, spm_equivalence_scale +from spm_calculator.geoadj import calculate_geoadj_from_rent + from policyengine_us.variables.household.demographic.geographic.state_name import ( StateName, ) @@ -177,6 +181,19 @@ 56: StateCode.WY, } +# SPM Tenure Type Mappings +SPM_TENURE_STRING_TO_CODE = { + "OWNER_WITH_MORTGAGE": 1, + "OWNER_WITHOUT_MORTGAGE": 2, + "RENTER": 3, +} + +SPM_TENURE_CODE_TO_CALC = { + 1: "owner_with_mortgage", + 2: "owner_without_mortgage", + 3: "renter", +} + def get_calculated_variables(sim) -> List[str]: """ @@ -416,3 +433,93 @@ def get_cd_index_mapping(db_uri: str = None): cd_to_index = {cd: idx for idx, cd in enumerate(cds_ordered)} index_to_cd = {idx: cd for idx, cd in enumerate(cds_ordered)} return cd_to_index, index_to_cd, cds_ordered + + +def load_cd_geoadj_values( + cds_to_calibrate: List[str], +) -> Dict[str, float]: + """ + Load geographic adjustment factors from rent data CSV. + Uses median 2BR rent by CD vs national median to compute geoadj. + """ + from policyengine_us_data.storage import STORAGE_FOLDER + + csv_path = STORAGE_FOLDER / "national_and_district_rents_2023.csv" + rent_df = pd.read_csv(csv_path, dtype={"cd_id": str}) + + # Filter to numeric cd_id only (excludes "09ZZ" style undefined districts) + rent_df = rent_df[rent_df["cd_id"].str.match(r"^\d+$")] + + # Convert zero-padded cd_id to match code format (e.g., "0101" -> "101") + rent_df["cd_geoid"] = rent_df["cd_id"].apply(lambda x: str(int(x))) + + # Filter to only CDs we're calibrating + rent_df = rent_df[rent_df["cd_geoid"].isin(cds_to_calibrate)] + + geoadj_dict = {} + for _, row in rent_df.iterrows(): + geoadj = calculate_geoadj_from_rent( + local_rent=row["median_2br_rent"], + national_rent=row["national_median_2br_rent"], + ) + geoadj_dict[row["cd_geoid"]] = geoadj + + return geoadj_dict + + +def calculate_spm_thresholds_for_cd( + sim, + time_period: int, + geoadj: float, + year: int, +) -> np.ndarray: + """ + Calculate SPM thresholds for all SPM units using CD-specific geo-adjustment. + """ + spm_unit_ids_person = sim.calculate("spm_unit_id", map_to="person").values + ages = sim.calculate("age", map_to="person").values + + df = pd.DataFrame({ + "spm_unit_id": spm_unit_ids_person, + "is_adult": ages >= 18, + "is_child": ages < 18, + }) + + agg = df.groupby("spm_unit_id").agg( + num_adults=("is_adult", "sum"), + num_children=("is_child", "sum"), + ).reset_index() + + tenure_types = sim.calculate( + "spm_unit_tenure_type", map_to="spm_unit" + ).values + spm_unit_ids_unit = sim.calculate("spm_unit_id", map_to="spm_unit").values + + tenure_df = pd.DataFrame({ + "spm_unit_id": spm_unit_ids_unit, + "tenure_type": tenure_types, + }) + + merged = agg.merge(tenure_df, on="spm_unit_id", how="left") + merged["tenure_code"] = merged["tenure_type"].map( + SPM_TENURE_STRING_TO_CODE + ).fillna(3).astype(int) + + calc = SPMCalculator(year=year) + base_thresholds = calc.get_base_thresholds() + + n = len(merged) + thresholds = np.zeros(n, dtype=np.float32) + + for i in range(n): + tenure_str = SPM_TENURE_CODE_TO_CALC.get( + int(merged.iloc[i]["tenure_code"]), "renter" + ) + base = base_thresholds[tenure_str] + equiv_scale = spm_equivalence_scale( + int(merged.iloc[i]["num_adults"]), + int(merged.iloc[i]["num_children"]), + ) + thresholds[i] = base * equiv_scale * geoadj + + return thresholds diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py index 1dc7f7be..1c4dbda1 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -17,6 +17,8 @@ STATE_CODES, STATE_FIPS_TO_NAME, STATE_FIPS_TO_CODE, + load_cd_geoadj_values, + calculate_spm_thresholds_for_cd, ) from policyengine_us.variables.household.demographic.geographic.county.county_enum import ( County, @@ -128,6 +130,8 @@ def create_sparse_cd_stacked_dataset( print(f"Total active household-CD pairs: {total_active_weights:,}") print(f"Total weight in W matrix: {total_weight_in_W:,.0f}") + cd_geoadj_values = load_cd_geoadj_values(cds_to_calibrate) + # Collect DataFrames for each CD cd_dfs = [] total_kept_households = 0 @@ -287,6 +291,14 @@ def create_sparse_cd_stacked_dataset( ) cd_sim.set_input("county", time_period, county_indices) + geoadj = cd_geoadj_values[cd_geoid] + new_spm_thresholds = calculate_spm_thresholds_for_cd( + cd_sim, time_period, geoadj, year=time_period + ) + cd_sim.set_input( + "spm_unit_spm_threshold", time_period, new_spm_thresholds + ) + # Delete cached calculated variables to ensure they're recalculated # with new state and county. Exclude 'county' itself since we just set it. for var in get_calculated_variables(cd_sim): @@ -580,6 +592,9 @@ def create_sparse_cd_stacked_dataset( # county is set explicitly with assign_counties_for_cd, must be saved vars_to_save.add("county") + # spm_unit_spm_threshold is recalculated with CD-specific geo-adjustment + vars_to_save.add("spm_unit_spm_threshold") + variables_saved = 0 variables_skipped = 0 diff --git a/policyengine_us_data/storage/README.md b/policyengine_us_data/storage/README.md index 2b0da6b9..80d4c1cd 100644 --- a/policyengine_us_data/storage/README.md +++ b/policyengine_us_data/storage/README.md @@ -28,4 +28,9 @@ • Locations: - https://www.ssa.gov/oact/tr/2025/lrIndex.html - `https://www.ssa.gov/oact/solvency/provisions/tables/table_run133.html` - • Notes: Contains OASDI cost projections and taxable payroll data (2025-2100) + • Notes: Contains OASDI cost projections and taxable payroll data (2025-2100) + +- **national_and_district_rents_2023.csv** + • Source: Census ACS 5-year estimates (2023), median 2BR rent by congressional district + • Created by: `fetch_cd_rents.py` (requires `CENSUS_API_KEY` environment variable) + • Notes: Used to calculate SPM geographic adjustment factors for local area calibration diff --git a/policyengine_us_data/storage/fetch_cd_rents.py b/policyengine_us_data/storage/fetch_cd_rents.py new file mode 100644 index 00000000..8d15a992 --- /dev/null +++ b/policyengine_us_data/storage/fetch_cd_rents.py @@ -0,0 +1,54 @@ +import os + +import requests +import pandas as pd + +# The user needs to have a CENSUS_API_KEY environment variable set to run this code +CENSUS_API_KEY = os.getenv("CENSUS_API_KEY") +YEAR = 2023 + +# Get national median 2BR rent +national_url = ( + f"https://api.census.gov/data/{YEAR}/acs/acs5" + f"?get=B25031_004E" + f"&for=us:*" + f"&key={CENSUS_API_KEY}" +) +national_rent = float(requests.get(national_url).json()[1][0]) +print(f"National median 2BR rent ({YEAR}): ${national_rent:,.0f}") + +# Get all congressional districts (loop through states) +all_rows = [] +for state_fips in range(1, 57): + state_str = f"{state_fips:02d}" + url = ( + f"https://api.census.gov/data/{YEAR}/acs/acs5" + f"?get=B25031_004E,NAME" + f"&for=congressional%20district:*" + f"&in=state:{state_str}" + f"&key={CENSUS_API_KEY}" + ) + try: + resp = requests.get(url) + if resp.status_code == 200: + data = resp.json() + for row in data[1:]: + all_rows.append({ + "state_fips": row[2], + "district": row[3], + "cd_id": row[2] + row[3], + "name": row[1], + "median_2br_rent": float(row[0]) if row[0] else None, + }) + except Exception as e: + pass + +df = pd.DataFrame(all_rows) +df["national_median_2br_rent"] = national_rent + +print(f"\nFetched {len(df)} congressional districts") +print(df.head(10)) + +outfile = f"./national_and_district_rents_{YEAR}.csv" +df.to_csv(outfile, index=False) +print(outfile) diff --git a/policyengine_us_data/storage/national_and_district_rents_2023.csv b/policyengine_us_data/storage/national_and_district_rents_2023.csv new file mode 100644 index 00000000..5f7fa5f3 --- /dev/null +++ b/policyengine_us_data/storage/national_and_district_rents_2023.csv @@ -0,0 +1,440 @@ +state_fips,district,cd_id,name,median_2br_rent,national_median_2br_rent +01,01,0101,"Congressional District 1 (118th Congress), Alabama",1000.0,1338.0 +01,02,0102,"Congressional District 2 (118th Congress), Alabama",879.0,1338.0 +01,03,0103,"Congressional District 3 (118th Congress), Alabama",830.0,1338.0 +01,04,0104,"Congressional District 4 (118th Congress), Alabama",731.0,1338.0 +01,05,0105,"Congressional District 5 (118th Congress), Alabama",932.0,1338.0 +01,06,0106,"Congressional District 6 (118th Congress), Alabama",1181.0,1338.0 +01,07,0107,"Congressional District 7 (118th Congress), Alabama",912.0,1338.0 +02,00,0200,"Congressional District (at Large) (118th Congress), Alaska",1399.0,1338.0 +04,01,0401,"Congressional District 1 (118th Congress), Arizona",1697.0,1338.0 +04,02,0402,"Congressional District 2 (118th Congress), Arizona",1195.0,1338.0 +04,03,0403,"Congressional District 3 (118th Congress), Arizona",1333.0,1338.0 +04,04,0404,"Congressional District 4 (118th Congress), Arizona",1592.0,1338.0 +04,05,0405,"Congressional District 5 (118th Congress), Arizona",1744.0,1338.0 +04,06,0406,"Congressional District 6 (118th Congress), Arizona",1239.0,1338.0 +04,07,0407,"Congressional District 7 (118th Congress), Arizona",1066.0,1338.0 +04,08,0408,"Congressional District 8 (118th Congress), Arizona",1507.0,1338.0 +04,09,0409,"Congressional District 9 (118th Congress), Arizona",1168.0,1338.0 +05,01,0501,"Congressional District 1 (118th Congress), Arkansas",789.0,1338.0 +05,02,0502,"Congressional District 2 (118th Congress), Arkansas",953.0,1338.0 +05,03,0503,"Congressional District 3 (118th Congress), Arkansas",954.0,1338.0 +05,04,0504,"Congressional District 4 (118th Congress), Arkansas",769.0,1338.0 +06,01,0601,"Congressional District 1 (118th Congress), California",1209.0,1338.0 +06,02,0602,"Congressional District 2 (118th Congress), California",1938.0,1338.0 +06,03,0603,"Congressional District 3 (118th Congress), California",1827.0,1338.0 +06,04,0604,"Congressional District 4 (118th Congress), California",2020.0,1338.0 +06,05,0605,"Congressional District 5 (118th Congress), California",1521.0,1338.0 +06,06,0606,"Congressional District 6 (118th Congress), California",1669.0,1338.0 +06,07,0607,"Congressional District 7 (118th Congress), California",1674.0,1338.0 +06,08,0608,"Congressional District 8 (118th Congress), California",2054.0,1338.0 +06,09,0609,"Congressional District 9 (118th Congress), California",1489.0,1338.0 +06,10,0610,"Congressional District 10 (118th Congress), California",2551.0,1338.0 +06,11,0611,"Congressional District 11 (118th Congress), California",3051.0,1338.0 +06,12,0612,"Congressional District 12 (118th Congress), California",2289.0,1338.0 +06,13,0613,"Congressional District 13 (118th Congress), California",1187.0,1338.0 +06,14,0614,"Congressional District 14 (118th Congress), California",2607.0,1338.0 +06,15,0615,"Congressional District 15 (118th Congress), California",3103.0,1338.0 +06,16,0616,"Congressional District 16 (118th Congress), California",2870.0,1338.0 +06,17,0617,"Congressional District 17 (118th Congress), California",3130.0,1338.0 +06,18,0618,"Congressional District 18 (118th Congress), California",2076.0,1338.0 +06,19,0619,"Congressional District 19 (118th Congress), California",2295.0,1338.0 +06,20,0620,"Congressional District 20 (118th Congress), California",1326.0,1338.0 +06,21,0621,"Congressional District 21 (118th Congress), California",1152.0,1338.0 +06,22,0622,"Congressional District 22 (118th Congress), California",1065.0,1338.0 +06,23,0623,"Congressional District 23 (118th Congress), California",1338.0,1338.0 +06,24,0624,"Congressional District 24 (118th Congress), California",2071.0,1338.0 +06,25,0625,"Congressional District 25 (118th Congress), California",1252.0,1338.0 +06,26,0626,"Congressional District 26 (118th Congress), California",2215.0,1338.0 +06,27,0627,"Congressional District 27 (118th Congress), California",1937.0,1338.0 +06,28,0628,"Congressional District 28 (118th Congress), California",2124.0,1338.0 +06,29,0629,"Congressional District 29 (118th Congress), California",2030.0,1338.0 +06,30,0630,"Congressional District 30 (118th Congress), California",2397.0,1338.0 +06,31,0631,"Congressional District 31 (118th Congress), California",1910.0,1338.0 +06,32,0632,"Congressional District 32 (118th Congress), California",2505.0,1338.0 +06,33,0633,"Congressional District 33 (118th Congress), California",1589.0,1338.0 +06,34,0634,"Congressional District 34 (118th Congress), California",1839.0,1338.0 +06,35,0635,"Congressional District 35 (118th Congress), California",1877.0,1338.0 +06,36,0636,"Congressional District 36 (118th Congress), California",2835.0,1338.0 +06,37,0637,"Congressional District 37 (118th Congress), California",1873.0,1338.0 +06,38,0638,"Congressional District 38 (118th Congress), California",1933.0,1338.0 +06,39,0639,"Congressional District 39 (118th Congress), California",1786.0,1338.0 +06,40,0640,"Congressional District 40 (118th Congress), California",2542.0,1338.0 +06,41,0641,"Congressional District 41 (118th Congress), California",1803.0,1338.0 +06,42,0642,"Congressional District 42 (118th Congress), California",1875.0,1338.0 +06,43,0643,"Congressional District 43 (118th Congress), California",1797.0,1338.0 +06,44,0644,"Congressional District 44 (118th Congress), California",1800.0,1338.0 +06,45,0645,"Congressional District 45 (118th Congress), California",2155.0,1338.0 +06,46,0646,"Congressional District 46 (118th Congress), California",2116.0,1338.0 +06,47,0647,"Congressional District 47 (118th Congress), California",2789.0,1338.0 +06,48,0648,"Congressional District 48 (118th Congress), California",1965.0,1338.0 +06,49,0649,"Congressional District 49 (118th Congress), California",2430.0,1338.0 +06,50,0650,"Congressional District 50 (118th Congress), California",2460.0,1338.0 +06,51,0651,"Congressional District 51 (118th Congress), California",2253.0,1338.0 +06,52,0652,"Congressional District 52 (118th Congress), California",1874.0,1338.0 +08,01,0801,"Congressional District 1 (118th Congress), Colorado",1958.0,1338.0 +08,02,0802,"Congressional District 2 (118th Congress), Colorado",1792.0,1338.0 +08,03,0803,"Congressional District 3 (118th Congress), Colorado",1109.0,1338.0 +08,04,0804,"Congressional District 4 (118th Congress), Colorado",1698.0,1338.0 +08,05,0805,"Congressional District 5 (118th Congress), Colorado",1500.0,1338.0 +08,06,0806,"Congressional District 6 (118th Congress), Colorado",1856.0,1338.0 +08,07,0807,"Congressional District 7 (118th Congress), Colorado",1783.0,1338.0 +08,08,0808,"Congressional District 8 (118th Congress), Colorado",1634.0,1338.0 +09,01,0901,"Congressional District 1 (118th Congress), Connecticut",1444.0,1338.0 +09,02,0902,"Congressional District 2 (118th Congress), Connecticut",1426.0,1338.0 +09,03,0903,"Congressional District 3 (118th Congress), Connecticut",1550.0,1338.0 +09,04,0904,"Congressional District 4 (118th Congress), Connecticut",1941.0,1338.0 +09,05,0905,"Congressional District 5 (118th Congress), Connecticut",1364.0,1338.0 +09,ZZ,09ZZ,"Congressional Districts not defined (118th Congress), Connecticut",-666666666.0,1338.0 +10,00,1000,"Congressional District (at Large) (118th Congress), Delaware",1329.0,1338.0 +11,98,1198,"Delegate District (at Large) (118th Congress), District of Columbia",1949.0,1338.0 +12,01,1201,"Congressional District 1 (118th Congress), Florida",1229.0,1338.0 +12,02,1202,"Congressional District 2 (118th Congress), Florida",1198.0,1338.0 +12,03,1203,"Congressional District 3 (118th Congress), Florida",1154.0,1338.0 +12,04,1204,"Congressional District 4 (118th Congress), Florida",1185.0,1338.0 +12,05,1205,"Congressional District 5 (118th Congress), Florida",1604.0,1338.0 +12,06,1206,"Congressional District 6 (118th Congress), Florida",1211.0,1338.0 +12,07,1207,"Congressional District 7 (118th Congress), Florida",1578.0,1338.0 +12,08,1208,"Congressional District 8 (118th Congress), Florida",1364.0,1338.0 +12,09,1209,"Congressional District 9 (118th Congress), Florida",1660.0,1338.0 +12,10,1210,"Congressional District 10 (118th Congress), Florida",1600.0,1338.0 +12,11,1211,"Congressional District 11 (118th Congress), Florida",1592.0,1338.0 +12,12,1212,"Congressional District 12 (118th Congress), Florida",1182.0,1338.0 +12,13,1213,"Congressional District 13 (118th Congress), Florida",1598.0,1338.0 +12,14,1214,"Congressional District 14 (118th Congress), Florida",1660.0,1338.0 +12,15,1215,"Congressional District 15 (118th Congress), Florida",1365.0,1338.0 +12,16,1216,"Congressional District 16 (118th Congress), Florida",1579.0,1338.0 +12,17,1217,"Congressional District 17 (118th Congress), Florida",1478.0,1338.0 +12,18,1218,"Congressional District 18 (118th Congress), Florida",1069.0,1338.0 +12,19,1219,"Congressional District 19 (118th Congress), Florida",1614.0,1338.0 +12,20,1220,"Congressional District 20 (118th Congress), Florida",1667.0,1338.0 +12,21,1221,"Congressional District 21 (118th Congress), Florida",1635.0,1338.0 +12,22,1222,"Congressional District 22 (118th Congress), Florida",1755.0,1338.0 +12,23,1223,"Congressional District 23 (118th Congress), Florida",1939.0,1338.0 +12,24,1224,"Congressional District 24 (118th Congress), Florida",1774.0,1338.0 +12,25,1225,"Congressional District 25 (118th Congress), Florida",1909.0,1338.0 +12,26,1226,"Congressional District 26 (118th Congress), Florida",1751.0,1338.0 +12,27,1227,"Congressional District 27 (118th Congress), Florida",1858.0,1338.0 +12,28,1228,"Congressional District 28 (118th Congress), Florida",1700.0,1338.0 +13,01,1301,"Congressional District 1 (118th Congress), Georgia",1130.0,1338.0 +13,02,1302,"Congressional District 2 (118th Congress), Georgia",883.0,1338.0 +13,03,1303,"Congressional District 3 (118th Congress), Georgia",1051.0,1338.0 +13,04,1304,"Congressional District 4 (118th Congress), Georgia",1550.0,1338.0 +13,05,1305,"Congressional District 5 (118th Congress), Georgia",1537.0,1338.0 +13,06,1306,"Congressional District 6 (118th Congress), Georgia",1744.0,1338.0 +13,07,1307,"Congressional District 7 (118th Congress), Georgia",1657.0,1338.0 +13,08,1308,"Congressional District 8 (118th Congress), Georgia",870.0,1338.0 +13,09,1309,"Congressional District 9 (118th Congress), Georgia",1137.0,1338.0 +13,10,1310,"Congressional District 10 (118th Congress), Georgia",1087.0,1338.0 +13,11,1311,"Congressional District 11 (118th Congress), Georgia",1604.0,1338.0 +13,12,1312,"Congressional District 12 (118th Congress), Georgia",928.0,1338.0 +13,13,1313,"Congressional District 13 (118th Congress), Georgia",1331.0,1338.0 +13,14,1314,"Congressional District 14 (118th Congress), Georgia",876.0,1338.0 +15,01,1501,"Congressional District 1 (118th Congress), Hawaii",2047.0,1338.0 +15,02,1502,"Congressional District 2 (118th Congress), Hawaii",1873.0,1338.0 +16,01,1601,"Congressional District 1 (118th Congress), Idaho",1087.0,1338.0 +16,02,1602,"Congressional District 2 (118th Congress), Idaho",1077.0,1338.0 +17,01,1701,"Congressional District 1 (118th Congress), Illinois",1158.0,1338.0 +17,02,1702,"Congressional District 2 (118th Congress), Illinois",1069.0,1338.0 +17,03,1703,"Congressional District 3 (118th Congress), Illinois",1442.0,1338.0 +17,04,1704,"Congressional District 4 (118th Congress), Illinois",1130.0,1338.0 +17,05,1705,"Congressional District 5 (118th Congress), Illinois",1894.0,1338.0 +17,06,1706,"Congressional District 6 (118th Congress), Illinois",1424.0,1338.0 +17,07,1707,"Congressional District 7 (118th Congress), Illinois",1433.0,1338.0 +17,08,1708,"Congressional District 8 (118th Congress), Illinois",1606.0,1338.0 +17,09,1709,"Congressional District 9 (118th Congress), Illinois",1632.0,1338.0 +17,10,1710,"Congressional District 10 (118th Congress), Illinois",1411.0,1338.0 +17,11,1711,"Congressional District 11 (118th Congress), Illinois",1594.0,1338.0 +17,12,1712,"Congressional District 12 (118th Congress), Illinois",816.0,1338.0 +17,13,1713,"Congressional District 13 (118th Congress), Illinois",979.0,1338.0 +17,14,1714,"Congressional District 14 (118th Congress), Illinois",1222.0,1338.0 +17,15,1715,"Congressional District 15 (118th Congress), Illinois",838.0,1338.0 +17,16,1716,"Congressional District 16 (118th Congress), Illinois",1003.0,1338.0 +17,17,1717,"Congressional District 17 (118th Congress), Illinois",904.0,1338.0 +17,ZZ,17ZZ,"Congressional Districts not defined (118th Congress), Illinois",-666666666.0,1338.0 +18,01,1801,"Congressional District 1 (118th Congress), Indiana",1091.0,1338.0 +18,02,1802,"Congressional District 2 (118th Congress), Indiana",999.0,1338.0 +18,03,1803,"Congressional District 3 (118th Congress), Indiana",923.0,1338.0 +18,04,1804,"Congressional District 4 (118th Congress), Indiana",1057.0,1338.0 +18,05,1805,"Congressional District 5 (118th Congress), Indiana",1028.0,1338.0 +18,06,1806,"Congressional District 6 (118th Congress), Indiana",1015.0,1338.0 +18,07,1807,"Congressional District 7 (118th Congress), Indiana",1134.0,1338.0 +18,08,1808,"Congressional District 8 (118th Congress), Indiana",931.0,1338.0 +18,09,1809,"Congressional District 9 (118th Congress), Indiana",994.0,1338.0 +19,01,1901,"Congressional District 1 (118th Congress), Iowa",958.0,1338.0 +19,02,1902,"Congressional District 2 (118th Congress), Iowa",922.0,1338.0 +19,03,1903,"Congressional District 3 (118th Congress), Iowa",1099.0,1338.0 +19,04,1904,"Congressional District 4 (118th Congress), Iowa",891.0,1338.0 +20,01,2001,"Congressional District 1 (118th Congress), Kansas",913.0,1338.0 +20,02,2002,"Congressional District 2 (118th Congress), Kansas",917.0,1338.0 +20,03,2003,"Congressional District 3 (118th Congress), Kansas",1365.0,1338.0 +20,04,2004,"Congressional District 4 (118th Congress), Kansas",954.0,1338.0 +21,01,2101,"Congressional District 1 (118th Congress), Kentucky",820.0,1338.0 +21,02,2102,"Congressional District 2 (118th Congress), Kentucky",874.0,1338.0 +21,03,2103,"Congressional District 3 (118th Congress), Kentucky",1119.0,1338.0 +21,04,2104,"Congressional District 4 (118th Congress), Kentucky",1047.0,1338.0 +21,05,2105,"Congressional District 5 (118th Congress), Kentucky",724.0,1338.0 +21,06,2106,"Congressional District 6 (118th Congress), Kentucky",963.0,1338.0 +22,01,2201,"Congressional District 1 (118th Congress), Louisiana",1220.0,1338.0 +22,02,2202,"Congressional District 2 (118th Congress), Louisiana",1156.0,1338.0 +22,03,2203,"Congressional District 3 (118th Congress), Louisiana",946.0,1338.0 +22,04,2204,"Congressional District 4 (118th Congress), Louisiana",870.0,1338.0 +22,05,2205,"Congressional District 5 (118th Congress), Louisiana",838.0,1338.0 +22,06,2206,"Congressional District 6 (118th Congress), Louisiana",1136.0,1338.0 +23,01,2301,"Congressional District 1 (118th Congress), Maine",1416.0,1338.0 +23,02,2302,"Congressional District 2 (118th Congress), Maine",985.0,1338.0 +24,01,2401,"Congressional District 1 (118th Congress), Maryland",1275.0,1338.0 +24,02,2402,"Congressional District 2 (118th Congress), Maryland",1664.0,1338.0 +24,03,2403,"Congressional District 3 (118th Congress), Maryland",1946.0,1338.0 +24,04,2404,"Congressional District 4 (118th Congress), Maryland",1785.0,1338.0 +24,05,2405,"Congressional District 5 (118th Congress), Maryland",1904.0,1338.0 +24,06,2406,"Congressional District 6 (118th Congress), Maryland",1626.0,1338.0 +24,07,2407,"Congressional District 7 (118th Congress), Maryland",1367.0,1338.0 +24,08,2408,"Congressional District 8 (118th Congress), Maryland",2094.0,1338.0 +25,01,2501,"Congressional District 1 (118th Congress), Massachusetts",1190.0,1338.0 +25,02,2502,"Congressional District 2 (118th Congress), Massachusetts",1494.0,1338.0 +25,03,2503,"Congressional District 3 (118th Congress), Massachusetts",1671.0,1338.0 +25,04,2504,"Congressional District 4 (118th Congress), Massachusetts",1626.0,1338.0 +25,05,2505,"Congressional District 5 (118th Congress), Massachusetts",2342.0,1338.0 +25,06,2506,"Congressional District 6 (118th Congress), Massachusetts",1962.0,1338.0 +25,07,2507,"Congressional District 7 (118th Congress), Massachusetts",2141.0,1338.0 +25,08,2508,"Congressional District 8 (118th Congress), Massachusetts",2218.0,1338.0 +25,09,2509,"Congressional District 9 (118th Congress), Massachusetts",1505.0,1338.0 +26,01,2601,"Congressional District 1 (118th Congress), Michigan",890.0,1338.0 +26,02,2602,"Congressional District 2 (118th Congress), Michigan",889.0,1338.0 +26,03,2603,"Congressional District 3 (118th Congress), Michigan",1188.0,1338.0 +26,04,2604,"Congressional District 4 (118th Congress), Michigan",1069.0,1338.0 +26,05,2605,"Congressional District 5 (118th Congress), Michigan",964.0,1338.0 +26,06,2606,"Congressional District 6 (118th Congress), Michigan",1448.0,1338.0 +26,07,2607,"Congressional District 7 (118th Congress), Michigan",1112.0,1338.0 +26,08,2608,"Congressional District 8 (118th Congress), Michigan",946.0,1338.0 +26,09,2609,"Congressional District 9 (118th Congress), Michigan",1053.0,1338.0 +26,10,2610,"Congressional District 10 (118th Congress), Michigan",1220.0,1338.0 +26,11,2611,"Congressional District 11 (118th Congress), Michigan",1404.0,1338.0 +26,12,2612,"Congressional District 12 (118th Congress), Michigan",1167.0,1338.0 +26,13,2613,"Congressional District 13 (118th Congress), Michigan",1010.0,1338.0 +27,01,2701,"Congressional District 1 (118th Congress), Minnesota",1004.0,1338.0 +27,02,2702,"Congressional District 2 (118th Congress), Minnesota",1512.0,1338.0 +27,03,2703,"Congressional District 3 (118th Congress), Minnesota",1644.0,1338.0 +27,04,2704,"Congressional District 4 (118th Congress), Minnesota",1440.0,1338.0 +27,05,2705,"Congressional District 5 (118th Congress), Minnesota",1506.0,1338.0 +27,06,2706,"Congressional District 6 (118th Congress), Minnesota",1194.0,1338.0 +27,07,2707,"Congressional District 7 (118th Congress), Minnesota",878.0,1338.0 +27,08,2708,"Congressional District 8 (118th Congress), Minnesota",1037.0,1338.0 +28,01,2801,"Congressional District 1 (118th Congress), Mississippi",827.0,1338.0 +28,02,2802,"Congressional District 2 (118th Congress), Mississippi",797.0,1338.0 +28,03,2803,"Congressional District 3 (118th Congress), Mississippi",963.0,1338.0 +28,04,2804,"Congressional District 4 (118th Congress), Mississippi",945.0,1338.0 +29,01,2901,"Congressional District 1 (118th Congress), Missouri",1117.0,1338.0 +29,02,2902,"Congressional District 2 (118th Congress), Missouri",1193.0,1338.0 +29,03,2903,"Congressional District 3 (118th Congress), Missouri",960.0,1338.0 +29,04,2904,"Congressional District 4 (118th Congress), Missouri",880.0,1338.0 +29,05,2905,"Congressional District 5 (118th Congress), Missouri",1152.0,1338.0 +29,06,2906,"Congressional District 6 (118th Congress), Missouri",907.0,1338.0 +29,07,2907,"Congressional District 7 (118th Congress), Missouri",899.0,1338.0 +29,08,2908,"Congressional District 8 (118th Congress), Missouri",793.0,1338.0 +30,01,3001,"Congressional District 1 (118th Congress), Montana",1111.0,1338.0 +30,02,3002,"Congressional District 2 (118th Congress), Montana",970.0,1338.0 +31,01,3101,"Congressional District 1 (118th Congress), Nebraska",1071.0,1338.0 +31,02,3102,"Congressional District 2 (118th Congress), Nebraska",1206.0,1338.0 +31,03,3103,"Congressional District 3 (118th Congress), Nebraska",856.0,1338.0 +32,01,3201,"Congressional District 1 (118th Congress), Nevada",1341.0,1338.0 +32,02,3202,"Congressional District 2 (118th Congress), Nevada",1415.0,1338.0 +32,03,3203,"Congressional District 3 (118th Congress), Nevada",1588.0,1338.0 +32,04,3204,"Congressional District 4 (118th Congress), Nevada",1325.0,1338.0 +33,01,3301,"Congressional District 1 (118th Congress), New Hampshire",1618.0,1338.0 +33,02,3302,"Congressional District 2 (118th Congress), New Hampshire",1479.0,1338.0 +33,ZZ,33ZZ,"Congressional Districts not defined (118th Congress), New Hampshire",-666666666.0,1338.0 +34,01,3401,"Congressional District 1 (118th Congress), New Jersey",1507.0,1338.0 +34,02,3402,"Congressional District 2 (118th Congress), New Jersey",1354.0,1338.0 +34,03,3403,"Congressional District 3 (118th Congress), New Jersey",1800.0,1338.0 +34,04,3404,"Congressional District 4 (118th Congress), New Jersey",1736.0,1338.0 +34,05,3405,"Congressional District 5 (118th Congress), New Jersey",2029.0,1338.0 +34,06,3406,"Congressional District 6 (118th Congress), New Jersey",1968.0,1338.0 +34,07,3407,"Congressional District 7 (118th Congress), New Jersey",1920.0,1338.0 +34,08,3408,"Congressional District 8 (118th Congress), New Jersey",1740.0,1338.0 +34,09,3409,"Congressional District 9 (118th Congress), New Jersey",1692.0,1338.0 +34,10,3410,"Congressional District 10 (118th Congress), New Jersey",1562.0,1338.0 +34,11,3411,"Congressional District 11 (118th Congress), New Jersey",2056.0,1338.0 +34,12,3412,"Congressional District 12 (118th Congress), New Jersey",1865.0,1338.0 +35,01,3501,"Congressional District 1 (118th Congress), New Mexico",1141.0,1338.0 +35,02,3502,"Congressional District 2 (118th Congress), New Mexico",855.0,1338.0 +35,03,3503,"Congressional District 3 (118th Congress), New Mexico",967.0,1338.0 +36,01,3601,"Congressional District 1 (118th Congress), New York",2257.0,1338.0 +36,02,3602,"Congressional District 2 (118th Congress), New York",2221.0,1338.0 +36,03,3603,"Congressional District 3 (118th Congress), New York",2272.0,1338.0 +36,04,3604,"Congressional District 4 (118th Congress), New York",2092.0,1338.0 +36,05,3605,"Congressional District 5 (118th Congress), New York",1800.0,1338.0 +36,06,3606,"Congressional District 6 (118th Congress), New York",2069.0,1338.0 +36,07,3607,"Congressional District 7 (118th Congress), New York",1988.0,1338.0 +36,08,3608,"Congressional District 8 (118th Congress), New York",1567.0,1338.0 +36,09,3609,"Congressional District 9 (118th Congress), New York",1802.0,1338.0 +36,10,3610,"Congressional District 10 (118th Congress), New York",2085.0,1338.0 +36,11,3611,"Congressional District 11 (118th Congress), New York",1791.0,1338.0 +36,12,3612,"Congressional District 12 (118th Congress), New York",3338.0,1338.0 +36,13,3613,"Congressional District 13 (118th Congress), New York",1511.0,1338.0 +36,14,3614,"Congressional District 14 (118th Congress), New York",1764.0,1338.0 +36,15,3615,"Congressional District 15 (118th Congress), New York",1452.0,1338.0 +36,16,3616,"Congressional District 16 (118th Congress), New York",1903.0,1338.0 +36,17,3617,"Congressional District 17 (118th Congress), New York",1966.0,1338.0 +36,18,3618,"Congressional District 18 (118th Congress), New York",1662.0,1338.0 +36,19,3619,"Congressional District 19 (118th Congress), New York",1081.0,1338.0 +36,20,3620,"Congressional District 20 (118th Congress), New York",1321.0,1338.0 +36,21,3621,"Congressional District 21 (118th Congress), New York",1031.0,1338.0 +36,22,3622,"Congressional District 22 (118th Congress), New York",1085.0,1338.0 +36,23,3623,"Congressional District 23 (118th Congress), New York",950.0,1338.0 +36,24,3624,"Congressional District 24 (118th Congress), New York",969.0,1338.0 +36,25,3625,"Congressional District 25 (118th Congress), New York",1204.0,1338.0 +36,26,3626,"Congressional District 26 (118th Congress), New York",1013.0,1338.0 +37,01,3701,"Congressional District 1 (118th Congress), North Carolina",868.0,1338.0 +37,02,3702,"Congressional District 2 (118th Congress), North Carolina",1501.0,1338.0 +37,03,3703,"Congressional District 3 (118th Congress), North Carolina",933.0,1338.0 +37,04,3704,"Congressional District 4 (118th Congress), North Carolina",1279.0,1338.0 +37,05,3705,"Congressional District 5 (118th Congress), North Carolina",898.0,1338.0 +37,06,3706,"Congressional District 6 (118th Congress), North Carolina",1015.0,1338.0 +37,07,3707,"Congressional District 7 (118th Congress), North Carolina",1077.0,1338.0 +37,08,3708,"Congressional District 8 (118th Congress), North Carolina",865.0,1338.0 +37,09,3709,"Congressional District 9 (118th Congress), North Carolina",996.0,1338.0 +37,10,3710,"Congressional District 10 (118th Congress), North Carolina",838.0,1338.0 +37,11,3711,"Congressional District 11 (118th Congress), North Carolina",1049.0,1338.0 +37,12,3712,"Congressional District 12 (118th Congress), North Carolina",1391.0,1338.0 +37,13,3713,"Congressional District 13 (118th Congress), North Carolina",1118.0,1338.0 +37,14,3714,"Congressional District 14 (118th Congress), North Carolina",1458.0,1338.0 +38,00,3800,"Congressional District (at Large) (118th Congress), North Dakota",934.0,1338.0 +39,01,3901,"Congressional District 1 (118th Congress), Ohio",1111.0,1338.0 +39,02,3902,"Congressional District 2 (118th Congress), Ohio",876.0,1338.0 +39,03,3903,"Congressional District 3 (118th Congress), Ohio",1242.0,1338.0 +39,04,3904,"Congressional District 4 (118th Congress), Ohio",938.0,1338.0 +39,05,3905,"Congressional District 5 (118th Congress), Ohio",873.0,1338.0 +39,06,3906,"Congressional District 6 (118th Congress), Ohio",809.0,1338.0 +39,07,3907,"Congressional District 7 (118th Congress), Ohio",1118.0,1338.0 +39,08,3908,"Congressional District 8 (118th Congress), Ohio",1030.0,1338.0 +39,09,3909,"Congressional District 9 (118th Congress), Ohio",926.0,1338.0 +39,10,3910,"Congressional District 10 (118th Congress), Ohio",973.0,1338.0 +39,11,3911,"Congressional District 11 (118th Congress), Ohio",998.0,1338.0 +39,12,3912,"Congressional District 12 (118th Congress), Ohio",940.0,1338.0 +39,13,3913,"Congressional District 13 (118th Congress), Ohio",969.0,1338.0 +39,14,3914,"Congressional District 14 (118th Congress), Ohio",948.0,1338.0 +39,15,3915,"Congressional District 15 (118th Congress), Ohio",1218.0,1338.0 +40,01,4001,"Congressional District 1 (118th Congress), Oklahoma",1068.0,1338.0 +40,02,4002,"Congressional District 2 (118th Congress), Oklahoma",795.0,1338.0 +40,03,4003,"Congressional District 3 (118th Congress), Oklahoma",881.0,1338.0 +40,04,4004,"Congressional District 4 (118th Congress), Oklahoma",962.0,1338.0 +40,05,4005,"Congressional District 5 (118th Congress), Oklahoma",1081.0,1338.0 +41,01,4101,"Congressional District 1 (118th Congress), Oregon",1733.0,1338.0 +41,02,4102,"Congressional District 2 (118th Congress), Oregon",1073.0,1338.0 +41,03,4103,"Congressional District 3 (118th Congress), Oregon",1616.0,1338.0 +41,04,4104,"Congressional District 4 (118th Congress), Oregon",1245.0,1338.0 +41,05,4105,"Congressional District 5 (118th Congress), Oregon",1538.0,1338.0 +41,06,4106,"Congressional District 6 (118th Congress), Oregon",1438.0,1338.0 +42,01,4201,"Congressional District 1 (118th Congress), Pennsylvania",1633.0,1338.0 +42,02,4202,"Congressional District 2 (118th Congress), Pennsylvania",1330.0,1338.0 +42,03,4203,"Congressional District 3 (118th Congress), Pennsylvania",1458.0,1338.0 +42,04,4204,"Congressional District 4 (118th Congress), Pennsylvania",1658.0,1338.0 +42,05,4205,"Congressional District 5 (118th Congress), Pennsylvania",1460.0,1338.0 +42,06,4206,"Congressional District 6 (118th Congress), Pennsylvania",1497.0,1338.0 +42,07,4207,"Congressional District 7 (118th Congress), Pennsylvania",1352.0,1338.0 +42,08,4208,"Congressional District 8 (118th Congress), Pennsylvania",992.0,1338.0 +42,09,4209,"Congressional District 9 (118th Congress), Pennsylvania",938.0,1338.0 +42,10,4210,"Congressional District 10 (118th Congress), Pennsylvania",1196.0,1338.0 +42,11,4211,"Congressional District 11 (118th Congress), Pennsylvania",1275.0,1338.0 +42,12,4212,"Congressional District 12 (118th Congress), Pennsylvania",1136.0,1338.0 +42,13,4213,"Congressional District 13 (118th Congress), Pennsylvania",891.0,1338.0 +42,14,4214,"Congressional District 14 (118th Congress), Pennsylvania",855.0,1338.0 +42,15,4215,"Congressional District 15 (118th Congress), Pennsylvania",884.0,1338.0 +42,16,4216,"Congressional District 16 (118th Congress), Pennsylvania",894.0,1338.0 +42,17,4217,"Congressional District 17 (118th Congress), Pennsylvania",1124.0,1338.0 +44,01,4401,"Congressional District 1 (118th Congress), Rhode Island",1336.0,1338.0 +44,02,4402,"Congressional District 2 (118th Congress), Rhode Island",1378.0,1338.0 +45,01,4501,"Congressional District 1 (118th Congress), South Carolina",1521.0,1338.0 +45,02,4502,"Congressional District 2 (118th Congress), South Carolina",1087.0,1338.0 +45,03,4503,"Congressional District 3 (118th Congress), South Carolina",883.0,1338.0 +45,04,4504,"Congressional District 4 (118th Congress), South Carolina",1136.0,1338.0 +45,05,4505,"Congressional District 5 (118th Congress), South Carolina",1007.0,1338.0 +45,06,4506,"Congressional District 6 (118th Congress), South Carolina",1166.0,1338.0 +45,07,4507,"Congressional District 7 (118th Congress), South Carolina",968.0,1338.0 +46,00,4600,"Congressional District (at Large) (118th Congress), South Dakota",950.0,1338.0 +47,01,4701,"Congressional District 1 (118th Congress), Tennessee",850.0,1338.0 +47,02,4702,"Congressional District 2 (118th Congress), Tennessee",1127.0,1338.0 +47,03,4703,"Congressional District 3 (118th Congress), Tennessee",986.0,1338.0 +47,04,4704,"Congressional District 4 (118th Congress), Tennessee",1034.0,1338.0 +47,05,4705,"Congressional District 5 (118th Congress), Tennessee",1516.0,1338.0 +47,06,4706,"Congressional District 6 (118th Congress), Tennessee",1155.0,1338.0 +47,07,4707,"Congressional District 7 (118th Congress), Tennessee",1209.0,1338.0 +47,08,4708,"Congressional District 8 (118th Congress), Tennessee",821.0,1338.0 +47,09,4709,"Congressional District 9 (118th Congress), Tennessee",1085.0,1338.0 +48,01,4801,"Congressional District 1 (118th Congress), Texas",1008.0,1338.0 +48,02,4802,"Congressional District 2 (118th Congress), Texas",1570.0,1338.0 +48,03,4803,"Congressional District 3 (118th Congress), Texas",1733.0,1338.0 +48,04,4804,"Congressional District 4 (118th Congress), Texas",1617.0,1338.0 +48,05,4805,"Congressional District 5 (118th Congress), Texas",1285.0,1338.0 +48,06,4806,"Congressional District 6 (118th Congress), Texas",1347.0,1338.0 +48,07,4807,"Congressional District 7 (118th Congress), Texas",1549.0,1338.0 +48,08,4808,"Congressional District 8 (118th Congress), Texas",1408.0,1338.0 +48,09,4809,"Congressional District 9 (118th Congress), Texas",1259.0,1338.0 +48,10,4810,"Congressional District 10 (118th Congress), Texas",1156.0,1338.0 +48,11,4811,"Congressional District 11 (118th Congress), Texas",1169.0,1338.0 +48,12,4812,"Congressional District 12 (118th Congress), Texas",1446.0,1338.0 +48,13,4813,"Congressional District 13 (118th Congress), Texas",1051.0,1338.0 +48,14,4814,"Congressional District 14 (118th Congress), Texas",1208.0,1338.0 +48,15,4815,"Congressional District 15 (118th Congress), Texas",947.0,1338.0 +48,16,4816,"Congressional District 16 (118th Congress), Texas",991.0,1338.0 +48,17,4817,"Congressional District 17 (118th Congress), Texas",1111.0,1338.0 +48,18,4818,"Congressional District 18 (118th Congress), Texas",1270.0,1338.0 +48,19,4819,"Congressional District 19 (118th Congress), Texas",1039.0,1338.0 +48,20,4820,"Congressional District 20 (118th Congress), Texas",1296.0,1338.0 +48,21,4821,"Congressional District 21 (118th Congress), Texas",1456.0,1338.0 +48,22,4822,"Congressional District 22 (118th Congress), Texas",1543.0,1338.0 +48,23,4823,"Congressional District 23 (118th Congress), Texas",1131.0,1338.0 +48,24,4824,"Congressional District 24 (118th Congress), Texas",1746.0,1338.0 +48,25,4825,"Congressional District 25 (118th Congress), Texas",1293.0,1338.0 +48,26,4826,"Congressional District 26 (118th Congress), Texas",1766.0,1338.0 +48,27,4827,"Congressional District 27 (118th Congress), Texas",1193.0,1338.0 +48,28,4828,"Congressional District 28 (118th Congress), Texas",1030.0,1338.0 +48,29,4829,"Congressional District 29 (118th Congress), Texas",1173.0,1338.0 +48,30,4830,"Congressional District 30 (118th Congress), Texas",1484.0,1338.0 +48,31,4831,"Congressional District 31 (118th Congress), Texas",1244.0,1338.0 +48,32,4832,"Congressional District 32 (118th Congress), Texas",1620.0,1338.0 +48,33,4833,"Congressional District 33 (118th Congress), Texas",1396.0,1338.0 +48,34,4834,"Congressional District 34 (118th Congress), Texas",864.0,1338.0 +48,35,4835,"Congressional District 35 (118th Congress), Texas",1424.0,1338.0 +48,36,4836,"Congressional District 36 (118th Congress), Texas",1272.0,1338.0 +48,37,4837,"Congressional District 37 (118th Congress), Texas",1802.0,1338.0 +48,38,4838,"Congressional District 38 (118th Congress), Texas",1663.0,1338.0 +49,01,4901,"Congressional District 1 (118th Congress), Utah",1253.0,1338.0 +49,02,4902,"Congressional District 2 (118th Congress), Utah",1315.0,1338.0 +49,03,4903,"Congressional District 3 (118th Congress), Utah",1305.0,1338.0 +49,04,4904,"Congressional District 4 (118th Congress), Utah",1470.0,1338.0 +50,00,5000,"Congressional District (at Large) (118th Congress), Vermont",1327.0,1338.0 +51,01,5101,"Congressional District 1 (118th Congress), Virginia",1492.0,1338.0 +51,02,5102,"Congressional District 2 (118th Congress), Virginia",1486.0,1338.0 +51,03,5103,"Congressional District 3 (118th Congress), Virginia",1241.0,1338.0 +51,04,5104,"Congressional District 4 (118th Congress), Virginia",1281.0,1338.0 +51,05,5105,"Congressional District 5 (118th Congress), Virginia",1004.0,1338.0 +51,06,5106,"Congressional District 6 (118th Congress), Virginia",998.0,1338.0 +51,07,5107,"Congressional District 7 (118th Congress), Virginia",1645.0,1338.0 +51,08,5108,"Congressional District 8 (118th Congress), Virginia",2270.0,1338.0 +51,09,5109,"Congressional District 9 (118th Congress), Virginia",800.0,1338.0 +51,10,5110,"Congressional District 10 (118th Congress), Virginia",2006.0,1338.0 +51,11,5111,"Congressional District 11 (118th Congress), Virginia",2304.0,1338.0 +53,01,5301,"Congressional District 1 (118th Congress), Washington",2242.0,1338.0 +53,02,5302,"Congressional District 2 (118th Congress), Washington",1636.0,1338.0 +53,03,5303,"Congressional District 3 (118th Congress), Washington",1435.0,1338.0 +53,04,5304,"Congressional District 4 (118th Congress), Washington",1129.0,1338.0 +53,05,5305,"Congressional District 5 (118th Congress), Washington",1163.0,1338.0 +53,06,5306,"Congressional District 6 (118th Congress), Washington",1612.0,1338.0 +53,07,5307,"Congressional District 7 (118th Congress), Washington",2313.0,1338.0 +53,08,5308,"Congressional District 8 (118th Congress), Washington",1746.0,1338.0 +53,09,5309,"Congressional District 9 (118th Congress), Washington",1909.0,1338.0 +53,10,5310,"Congressional District 10 (118th Congress), Washington",1659.0,1338.0 +54,01,5401,"Congressional District 1 (118th Congress), West Virginia",830.0,1338.0 +54,02,5402,"Congressional District 2 (118th Congress), West Virginia",906.0,1338.0 +55,01,5501,"Congressional District 1 (118th Congress), Wisconsin",1116.0,1338.0 +55,02,5502,"Congressional District 2 (118th Congress), Wisconsin",1336.0,1338.0 +55,03,5503,"Congressional District 3 (118th Congress), Wisconsin",959.0,1338.0 +55,04,5504,"Congressional District 4 (118th Congress), Wisconsin",1071.0,1338.0 +55,05,5505,"Congressional District 5 (118th Congress), Wisconsin",1203.0,1338.0 +55,06,5506,"Congressional District 6 (118th Congress), Wisconsin",948.0,1338.0 +55,07,5507,"Congressional District 7 (118th Congress), Wisconsin",919.0,1338.0 +55,08,5508,"Congressional District 8 (118th Congress), Wisconsin",981.0,1338.0 +56,00,5600,"Congressional District (at Large) (118th Congress), Wyoming",932.0,1338.0 From af4904e14df1bc3766d6c67d2813805a2939e90f Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Wed, 17 Dec 2025 10:56:02 -0500 Subject: [PATCH 16/24] NYC workflow --- .github/workflows/local_area_publish.yaml | 3 + .../publish_local_area.py | 107 +++++++++++++++++- .../stacked_dataset_builder.py | 77 ++++++++++++- 3 files changed, 183 insertions(+), 4 deletions(-) diff --git a/.github/workflows/local_area_publish.yaml b/.github/workflows/local_area_publish.yaml index 6bf02987..5a87d3f6 100644 --- a/.github/workflows/local_area_publish.yaml +++ b/.github/workflows/local_area_publish.yaml @@ -50,6 +50,7 @@ jobs: run: | gsutil cp gs://policyengine-us-data/checkpoints/completed_states.txt . || true gsutil cp gs://policyengine-us-data/checkpoints/completed_districts.txt . || true + gsutil cp gs://policyengine-us-data/checkpoints/completed_cities.txt . || true - name: Build and publish local area H5 files run: make publish-local-area @@ -59,9 +60,11 @@ jobs: run: | gsutil cp completed_states.txt gs://policyengine-us-data/checkpoints/ || true gsutil cp completed_districts.txt gs://policyengine-us-data/checkpoints/ || true + gsutil cp completed_cities.txt gs://policyengine-us-data/checkpoints/ || true - name: Clean up checkpoints on success if: success() run: | gsutil rm gs://policyengine-us-data/checkpoints/completed_states.txt || true gsutil rm gs://policyengine-us-data/checkpoints/completed_districts.txt || true + gsutil rm gs://policyengine-us-data/checkpoints/completed_cities.txt || true diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py b/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py index eefc36ab..6941d412 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py @@ -17,6 +17,8 @@ from policyengine_us_data.utils.data_upload import upload_local_area_file from policyengine_us_data.datasets.cps.local_area_calibration.stacked_dataset_builder import ( create_sparse_cd_stacked_dataset, + NYC_COUNTIES, + NYC_CDS, ) from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( get_all_cds_from_database, @@ -25,6 +27,7 @@ CHECKPOINT_FILE = Path("completed_states.txt") CHECKPOINT_FILE_DISTRICTS = Path("completed_districts.txt") +CHECKPOINT_FILE_CITIES = Path("completed_cities.txt") WORK_DIR = Path("local_area_build") @@ -54,6 +57,19 @@ def record_completed_district(district_name: str): f.write(f"{district_name}\n") +def load_completed_cities() -> set: + if CHECKPOINT_FILE_CITIES.exists(): + content = CHECKPOINT_FILE_CITIES.read_text().strip() + if content: + return set(content.split("\n")) + return set() + + +def record_completed_city(city_name: str): + with open(CHECKPOINT_FILE_CITIES, "a") as f: + f.write(f"{city_name}\n") + + def build_and_upload_states( weights_path: Path, dataset_path: Path, @@ -157,6 +173,55 @@ def build_and_upload_districts( raise +def build_and_upload_cities( + weights_path: Path, + dataset_path: Path, + db_path: Path, + output_dir: Path, + completed_cities: set, +): + """Build and upload city H5 files with checkpointing.""" + db_uri = f"sqlite:///{db_path}" + cds_to_calibrate = get_all_cds_from_database(db_uri) + w = np.load(weights_path) + + cities_dir = output_dir / "cities" + cities_dir.mkdir(parents=True, exist_ok=True) + + # NYC + if "NYC" in completed_cities: + print("Skipping NYC (already completed)") + else: + cd_subset = [cd for cd in cds_to_calibrate if cd in NYC_CDS] + if not cd_subset: + print("No NYC-related CDs found, skipping") + else: + output_path = cities_dir / "NYC.h5" + print(f"\n{'='*60}") + print(f"Building NYC ({len(cd_subset)} CDs)") + print(f"{'='*60}") + + try: + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=cd_subset, + dataset_path=str(dataset_path), + output_path=str(output_path), + county_filter=NYC_COUNTIES, + ) + + print("Uploading NYC.h5...") + upload_local_area_file(str(output_path), "cities") + + record_completed_city("NYC") + print("Completed NYC") + + except Exception as e: + print(f"ERROR building NYC: {e}") + raise + + def main(): import argparse @@ -178,6 +243,11 @@ def main(): action="store_true", help="Only build and upload district files", ) + parser.add_argument( + "--cities-only", + action="store_true", + help="Only build and upload city files (e.g., NYC)", + ) parser.add_argument( "--weights-path", type=str, @@ -227,7 +297,26 @@ def main(): n_hh = sim.calculate("household_id", map_to="household").shape[0] print(f"\nBase dataset has {n_hh:,} households") - if not args.districts_only: + # Determine what to build based on flags + build_states = not args.districts_only and not args.cities_only + build_districts = not args.states_only and not args.cities_only + build_cities = not args.states_only and not args.districts_only + + # If a specific *-only flag is set, only build that type + if args.states_only: + build_states = True + build_districts = False + build_cities = False + elif args.districts_only: + build_states = False + build_districts = True + build_cities = False + elif args.cities_only: + build_states = False + build_districts = False + build_cities = True + + if build_states: print("\n" + "=" * 60) print("BUILDING STATE FILES") print("=" * 60) @@ -241,7 +330,7 @@ def main(): completed_states, ) - if not args.states_only: + if build_districts: print("\n" + "=" * 60) print("BUILDING DISTRICT FILES") print("=" * 60) @@ -255,6 +344,20 @@ def main(): completed_districts, ) + if build_cities: + print("\n" + "=" * 60) + print("BUILDING CITY FILES") + print("=" * 60) + completed_cities = load_completed_cities() + print(f"Already completed: {len(completed_cities)} cities") + build_and_upload_cities( + inputs["weights"], + inputs["dataset"], + inputs["database"], + WORK_DIR, + completed_cities, + ) + print("\n" + "=" * 60) print("ALL DONE!") print("=" * 60) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py index 1c4dbda1..e93a9e25 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -27,6 +27,35 @@ assign_counties_for_cd, ) +NYC_COUNTIES = { + "QUEENS_COUNTY_NY", + "BRONX_COUNTY_NY", + "RICHMOND_COUNTY_NY", + "NEW_YORK_COUNTY_NY", + "KINGS_COUNTY_NY", +} + +NYC_CDS = [ + "3603", + "3605", + "3606", + "3607", + "3608", + "3609", + "3610", + "3611", + "3612", + "3613", + "3614", + "3615", + "3616", +] + + +def get_county_name(county_index: int) -> str: + """Convert county enum index back to name.""" + return County._member_names_[county_index] + def create_sparse_cd_stacked_dataset( w, @@ -34,6 +63,7 @@ def create_sparse_cd_stacked_dataset( cd_subset=None, output_path=None, dataset_path=None, + county_filter=None, ): """ Create a SPARSE congressional district-stacked dataset using DataFrame approach. @@ -47,6 +77,8 @@ def create_sparse_cd_stacked_dataset( cds_to_calibrate). If None, includes all CDs. output_path: Where to save the sparse CD-stacked .h5 file. dataset_path: Path to the base .h5 dataset used during calibration. + county_filter: Optional set of county names to filter to. Only households + assigned to these counties will be included. Used for city-level datasets. Returns: output_path: Path to the saved .h5 file. @@ -291,6 +323,19 @@ def create_sparse_cd_stacked_dataset( ) cd_sim.set_input("county", time_period, county_indices) + # Filter to only households assigned to specified counties (e.g., NYC) + if county_filter is not None: + filtered_household_ids = set() + for hh_idx in active_household_indices: + county_name = get_county_name(county_indices[hh_idx]) + if county_name in county_filter: + filtered_household_ids.add(household_ids[hh_idx]) + + active_household_ids = filtered_household_ids + + if len(active_household_ids) == 0: + continue + geoadj = cd_geoadj_values[cd_geoid] new_spm_thresholds = calculate_spm_thresholds_for_cd( cd_sim, time_period, geoadj, year=time_period @@ -713,9 +758,16 @@ def create_sparse_cd_stacked_dataset( ) parser.add_argument( "--mode", - choices=["national", "states", "cds", "single-cd", "single-state"], + choices=[ + "national", + "states", + "cds", + "single-cd", + "single-state", + "nyc", + ], default="national", - help="Output mode: national (one file), states (per-state files), cds (per-CD files), single-cd (one CD), single-state (one state)", + help="Output mode: national (one file), states (per-state files), cds (per-CD files), single-cd (one CD), single-state (one state), nyc (NYC only)", ) parser.add_argument( "--cd", @@ -849,4 +901,25 @@ def create_sparse_cd_stacked_dataset( output_path=output_path, ) + elif mode == "nyc": + cd_subset = [cd for cd in cds_to_calibrate if cd in NYC_CDS] + if not cd_subset: + raise ValueError("No NYC-related CDs found in calibrated CDs list") + + output_path = f"{output_dir}/NYC.h5" + print( + f"\nCreating NYC dataset with {len(cd_subset)} CDs: {output_path}" + ) + print(f" CDs: {', '.join(cd_subset)}") + print(" Filtering to NYC counties only") + + create_sparse_cd_stacked_dataset( + w, + cds_to_calibrate, + cd_subset=cd_subset, + dataset_path=dataset_path_str, + output_path=output_path, + county_filter=NYC_COUNTIES, + ) + print("\nDone!") From 5c049d4c388a54baf5d236f8fce2f848ae67e209 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Thu, 18 Dec 2025 09:22:44 -0500 Subject: [PATCH 17/24] Add batched HuggingFace uploads and fix at-large district geoadj lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add upload_local_area_batch_to_hf() to batch multiple files per commit - Add skip_hf parameter to upload_local_area_file() for GCP-only uploads - Modify publish_local_area.py to batch HF uploads (10 files per commit) - Fix at-large district geoadj lookup (XX01 -> XX00 mapping for AK, DE, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../calibration_utils.py | 71 ++++++++++++------ .../publish_local_area.py | 74 +++++++++++++++++-- policyengine_us_data/utils/data_upload.py | 57 ++++++++++++++ 3 files changed, 174 insertions(+), 28 deletions(-) diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index e5058133..a9816772 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -453,16 +453,34 @@ def load_cd_geoadj_values( # Convert zero-padded cd_id to match code format (e.g., "0101" -> "101") rent_df["cd_geoid"] = rent_df["cd_id"].apply(lambda x: str(int(x))) - # Filter to only CDs we're calibrating - rent_df = rent_df[rent_df["cd_geoid"].isin(cds_to_calibrate)] - - geoadj_dict = {} + # Build lookup from rent data + rent_lookup = {} for _, row in rent_df.iterrows(): geoadj = calculate_geoadj_from_rent( local_rent=row["median_2br_rent"], national_rent=row["national_median_2br_rent"], ) - geoadj_dict[row["cd_geoid"]] = geoadj + rent_lookup[row["cd_geoid"]] = geoadj + + # Map each CD to calibrate to its geoadj value + # Handle at-large districts: database uses XX01, rent CSV uses XX00 + geoadj_dict = {} + for cd in cds_to_calibrate: + if cd in rent_lookup: + geoadj_dict[cd] = rent_lookup[cd] + else: + # Try at-large mapping: XX01 -> XX00 + cd_int = int(cd) + state_fips = cd_int // 100 + district = cd_int % 100 + if district == 1: + at_large_cd = str(state_fips * 100) # XX00 + if at_large_cd in rent_lookup: + geoadj_dict[cd] = rent_lookup[at_large_cd] + continue + # Fallback to national average (geoadj = 1.0) + print(f"Warning: No rent data for CD {cd}, using geoadj=1.0") + geoadj_dict[cd] = 1.0 return geoadj_dict @@ -479,31 +497,42 @@ def calculate_spm_thresholds_for_cd( spm_unit_ids_person = sim.calculate("spm_unit_id", map_to="person").values ages = sim.calculate("age", map_to="person").values - df = pd.DataFrame({ - "spm_unit_id": spm_unit_ids_person, - "is_adult": ages >= 18, - "is_child": ages < 18, - }) + df = pd.DataFrame( + { + "spm_unit_id": spm_unit_ids_person, + "is_adult": ages >= 18, + "is_child": ages < 18, + } + ) - agg = df.groupby("spm_unit_id").agg( - num_adults=("is_adult", "sum"), - num_children=("is_child", "sum"), - ).reset_index() + agg = ( + df.groupby("spm_unit_id") + .agg( + num_adults=("is_adult", "sum"), + num_children=("is_child", "sum"), + ) + .reset_index() + ) tenure_types = sim.calculate( "spm_unit_tenure_type", map_to="spm_unit" ).values spm_unit_ids_unit = sim.calculate("spm_unit_id", map_to="spm_unit").values - tenure_df = pd.DataFrame({ - "spm_unit_id": spm_unit_ids_unit, - "tenure_type": tenure_types, - }) + tenure_df = pd.DataFrame( + { + "spm_unit_id": spm_unit_ids_unit, + "tenure_type": tenure_types, + } + ) merged = agg.merge(tenure_df, on="spm_unit_id", how="left") - merged["tenure_code"] = merged["tenure_type"].map( - SPM_TENURE_STRING_TO_CODE - ).fillna(3).astype(int) + merged["tenure_code"] = ( + merged["tenure_type"] + .map(SPM_TENURE_STRING_TO_CODE) + .fillna(3) + .astype(int) + ) calc = SPMCalculator(year=year) base_thresholds = calc.get_base_thresholds() diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py b/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py index 6941d412..e798addf 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/publish_local_area.py @@ -14,7 +14,10 @@ from policyengine_us import Microsimulation from policyengine_us_data.utils.huggingface import download_calibration_inputs -from policyengine_us_data.utils.data_upload import upload_local_area_file +from policyengine_us_data.utils.data_upload import ( + upload_local_area_file, + upload_local_area_batch_to_hf, +) from policyengine_us_data.datasets.cps.local_area_calibration.stacked_dataset_builder import ( create_sparse_cd_stacked_dataset, NYC_COUNTIES, @@ -76,6 +79,7 @@ def build_and_upload_states( db_path: Path, output_dir: Path, completed_states: set, + hf_batch_size: int = 10, ): """Build and upload state H5 files with checkpointing.""" db_uri = f"sqlite:///{db_path}" @@ -85,6 +89,8 @@ def build_and_upload_states( states_dir = output_dir / "states" states_dir.mkdir(parents=True, exist_ok=True) + hf_queue = [] # Queue for batched HuggingFace uploads + for state_fips, state_code in STATE_CODES.items(): if state_code in completed_states: print(f"Skipping {state_code} (already completed)") @@ -111,16 +117,34 @@ def build_and_upload_states( output_path=str(output_path), ) - print(f"Uploading {state_code}.h5...") - upload_local_area_file(str(output_path), "states") + print(f"Uploading {state_code}.h5 to GCP...") + upload_local_area_file(str(output_path), "states", skip_hf=True) + + # Queue for batched HuggingFace upload + hf_queue.append((str(output_path), "states")) record_completed_state(state_code) print(f"Completed {state_code}") + # Flush HF queue every batch_size files + if len(hf_queue) >= hf_batch_size: + print( + f"\nUploading batch of {len(hf_queue)} files to HuggingFace..." + ) + upload_local_area_batch_to_hf(hf_queue) + hf_queue = [] + except Exception as e: print(f"ERROR building {state_code}: {e}") raise + # Flush remaining files to HuggingFace + if hf_queue: + print( + f"\nUploading final batch of {len(hf_queue)} files to HuggingFace..." + ) + upload_local_area_batch_to_hf(hf_queue) + def build_and_upload_districts( weights_path: Path, @@ -128,6 +152,7 @@ def build_and_upload_districts( db_path: Path, output_dir: Path, completed_districts: set, + hf_batch_size: int = 10, ): """Build and upload district H5 files with checkpointing.""" db_uri = f"sqlite:///{db_path}" @@ -137,6 +162,8 @@ def build_and_upload_districts( districts_dir = output_dir / "districts" districts_dir.mkdir(parents=True, exist_ok=True) + hf_queue = [] # Queue for batched HuggingFace uploads + for i, cd_geoid in enumerate(cds_to_calibrate): cd_int = int(cd_geoid) state_fips = cd_int // 100 @@ -162,16 +189,34 @@ def build_and_upload_districts( output_path=str(output_path), ) - print(f"Uploading {friendly_name}.h5...") - upload_local_area_file(str(output_path), "districts") + print(f"Uploading {friendly_name}.h5 to GCP...") + upload_local_area_file(str(output_path), "districts", skip_hf=True) + + # Queue for batched HuggingFace upload + hf_queue.append((str(output_path), "districts")) record_completed_district(friendly_name) print(f"Completed {friendly_name}") + # Flush HF queue every batch_size files + if len(hf_queue) >= hf_batch_size: + print( + f"\nUploading batch of {len(hf_queue)} files to HuggingFace..." + ) + upload_local_area_batch_to_hf(hf_queue) + hf_queue = [] + except Exception as e: print(f"ERROR building {friendly_name}: {e}") raise + # Flush remaining files to HuggingFace + if hf_queue: + print( + f"\nUploading final batch of {len(hf_queue)} files to HuggingFace..." + ) + upload_local_area_batch_to_hf(hf_queue) + def build_and_upload_cities( weights_path: Path, @@ -179,6 +224,7 @@ def build_and_upload_cities( db_path: Path, output_dir: Path, completed_cities: set, + hf_batch_size: int = 10, ): """Build and upload city H5 files with checkpointing.""" db_uri = f"sqlite:///{db_path}" @@ -188,6 +234,8 @@ def build_and_upload_cities( cities_dir = output_dir / "cities" cities_dir.mkdir(parents=True, exist_ok=True) + hf_queue = [] # Queue for batched HuggingFace uploads + # NYC if "NYC" in completed_cities: print("Skipping NYC (already completed)") @@ -211,8 +259,13 @@ def build_and_upload_cities( county_filter=NYC_COUNTIES, ) - print("Uploading NYC.h5...") - upload_local_area_file(str(output_path), "cities") + print("Uploading NYC.h5 to GCP...") + upload_local_area_file( + str(output_path), "cities", skip_hf=True + ) + + # Queue for batched HuggingFace upload + hf_queue.append((str(output_path), "cities")) record_completed_city("NYC") print("Completed NYC") @@ -221,6 +274,13 @@ def build_and_upload_cities( print(f"ERROR building NYC: {e}") raise + # Flush remaining files to HuggingFace + if hf_queue: + print( + f"\nUploading batch of {len(hf_queue)} city files to HuggingFace..." + ) + upload_local_area_batch_to_hf(hf_queue) + def main(): import argparse diff --git a/policyengine_us_data/utils/data_upload.py b/policyengine_us_data/utils/data_upload.py index 20aa1b65..039364a1 100644 --- a/policyengine_us_data/utils/data_upload.py +++ b/policyengine_us_data/utils/data_upload.py @@ -125,12 +125,16 @@ def upload_local_area_file( hf_repo_name: str = "policyengine/policyengine-us-data", hf_repo_type: str = "model", version: str = None, + skip_hf: bool = False, ): """ Upload a single local area H5 file to a subdirectory (states/ or districts/). Uploads to both GCS and Hugging Face with the file placed in the specified subdirectory. + + Args: + skip_hf: If True, skip HuggingFace upload (for batched uploads later) """ if version is None: version = metadata.version("policyengine-us-data") @@ -153,6 +157,9 @@ def upload_local_area_file( blob.patch() logging.info(f"Uploaded {blob_name} to GCS bucket {gcs_bucket_name}.") + if skip_hf: + return + # Upload to Hugging Face with subdirectory token = os.environ.get("HUGGING_FACE_TOKEN") api = HfApi() @@ -167,3 +174,53 @@ def upload_local_area_file( logging.info( f"Uploaded {subdirectory}/{file_path.name} to Hugging Face {hf_repo_name}." ) + + +def upload_local_area_batch_to_hf( + files_with_subdirs: List[tuple], + hf_repo_name: str = "policyengine/policyengine-us-data", + hf_repo_type: str = "model", + version: str = None, +): + """ + Upload multiple local area files to HuggingFace in a single commit. + + Args: + files_with_subdirs: List of (file_path, subdirectory) tuples + hf_repo_name: HuggingFace repository name + hf_repo_type: Repository type + version: Version string for commit message + """ + if version is None: + version = metadata.version("policyengine-us-data") + + token = os.environ.get("HUGGING_FACE_TOKEN") + api = HfApi() + + operations = [] + for file_path, subdirectory in files_with_subdirs: + file_path = Path(file_path) + if not file_path.exists(): + logging.warning(f"File {file_path} does not exist, skipping.") + continue + operations.append( + CommitOperationAdd( + path_in_repo=f"{subdirectory}/{file_path.name}", + path_or_fileobj=str(file_path), + ) + ) + + if not operations: + logging.warning("No files to upload to HuggingFace.") + return + + api.create_commit( + token=token, + repo_id=hf_repo_name, + operations=operations, + repo_type=hf_repo_type, + commit_message=f"Upload {len(operations)} local area files for version {version}", + ) + logging.info( + f"Uploaded {len(operations)} files to Hugging Face {hf_repo_name} in single commit." + ) From 56024b388c69b41552e1f8032f68c929a72eeb01 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Thu, 18 Dec 2025 15:39:37 -0500 Subject: [PATCH 18/24] Filter pseudo-input variables from H5 output and add checkpoint files to gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pseudo-inputs are variables with adds/subtracts that aggregate formula-based components. Saving their stale pre-computed values corrupts calculations when the dataset is reloaded. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 3 ++ .../calibration_utils.py | 39 +++++++++++++++++++ .../create_stratified_cps.py | 17 ++++++-- .../stacked_dataset_builder.py | 15 +++++-- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 64914192..5d183bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,8 @@ node_modules !policyengine_us_data/storage/national_and_district_rents_2023.csv docs/.ipynb_checkpoints/ +## Batch processing checkpoints +completed_*.txt + ## Test fixtures !policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5 diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py index a9816772..9a99d58d 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/calibration_utils.py @@ -229,6 +229,45 @@ def get_calculated_variables(sim) -> List[str]: ] +def get_pseudo_input_variables(sim) -> set: + """ + Identify pseudo-input variables that should NOT be saved to H5 files. + + A pseudo-input is a variable that: + - Appears in sim.input_variables (has stored values) + - Has 'adds' or 'subtracts' attribute + - At least one component has a formula (is calculated) + + These variables have stale pre-computed values that corrupt calculations + when reloaded, because the stored value overrides the formula. + """ + tbs = sim.tax_benefit_system + pseudo_inputs = set() + + for var_name in sim.input_variables: + var = tbs.variables.get(var_name) + if not var: + continue + + adds = getattr(var, "adds", None) + if adds and isinstance(adds, list): + for component in adds: + comp_var = tbs.variables.get(component) + if comp_var and len(getattr(comp_var, "formulas", {})) > 0: + pseudo_inputs.add(var_name) + break + + subtracts = getattr(var, "subtracts", None) + if subtracts and isinstance(subtracts, list): + for component in subtracts: + comp_var = tbs.variables.get(component) + if comp_var and len(getattr(comp_var, "formulas", {})) > 0: + pseudo_inputs.add(var_name) + break + + return pseudo_inputs + + def apply_op(values: np.ndarray, op: str, val: str) -> np.ndarray: """Apply constraint operation to values array.""" try: diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py index 3b3c51cd..d9507d17 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py @@ -14,6 +14,9 @@ from policyengine_us import Microsimulation from policyengine_core.data.dataset import Dataset from policyengine_core.enums import Enum +from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( + get_pseudo_input_variables, +) def create_stratified_cps_dataset( @@ -202,9 +205,17 @@ def create_stratified_cps_dataset( # Only save input variables (not calculated/derived variables) input_vars = set(sim.input_variables) - print( - f"Found {len(input_vars)} input variables (excluding calculated variables)" - ) + + # Filter out pseudo-inputs: variables with adds/subtracts that aggregate + # formula-based components. These have stale values that corrupt calculations. + pseudo_inputs = get_pseudo_input_variables(sim) + if pseudo_inputs: + print(f"Excluding {len(pseudo_inputs)} pseudo-input variables:") + for var in sorted(pseudo_inputs): + print(f" - {var}") + input_vars = input_vars - pseudo_inputs + + print(f"Found {len(input_vars)} input variables to save") for variable in stratified_sim.tax_benefit_system.variables: if variable not in input_vars: diff --git a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py index e93a9e25..c731e6fc 100644 --- a/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py +++ b/policyengine_us_data/datasets/cps/local_area_calibration/stacked_dataset_builder.py @@ -14,6 +14,7 @@ from policyengine_us_data.datasets.cps.local_area_calibration.calibration_utils import ( get_all_cds_from_database, get_calculated_variables, + get_pseudo_input_variables, STATE_CODES, STATE_FIPS_TO_NAME, STATE_FIPS_TO_CODE, @@ -624,11 +625,17 @@ def create_sparse_cd_stacked_dataset( # Only save input variables (not calculated/derived variables) # Calculated variables like state_name, state_code will be recalculated on load input_vars = set(base_sim.input_variables) - print( - f"Found {len(input_vars)} input variables (excluding calculated variables)" - ) - vars_to_save = input_vars.copy() + # Filter out pseudo-inputs: variables with adds/subtracts that aggregate + # formula-based components. These have stale values that corrupt calculations. + pseudo_inputs = get_pseudo_input_variables(base_sim) + if pseudo_inputs: + print(f"Excluding {len(pseudo_inputs)} pseudo-input variables:") + for var in sorted(pseudo_inputs): + print(f" - {var}") + + vars_to_save = input_vars - pseudo_inputs + print(f"Found {len(vars_to_save)} input variables to save") # congressional_district_geoid isn't in the original microdata and has no formula, # so it's not in input_vars. Since we set it explicitly during stacking, save it. From 911ad101c30f04b93796009866758c6ed58097db Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Thu, 18 Dec 2025 22:39:56 -0500 Subject: [PATCH 19/24] Add spm-calculator as a dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 495feafe..14acb68e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "sqlalchemy>=2.0.41", "sqlmodel>=0.0.24", "xlrd>=2.0.2", + "spm-calculator>=0.1.0", ] [project.optional-dependencies] From f452413697fcbfe3a039c629f49ec074e82a8472 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Mon, 29 Dec 2025 10:01:09 -0500 Subject: [PATCH 20/24] Fix database path and add download dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update notebook to use correct db path: storage/calibration/policy_data.db - Add download as dependency of data target in Makefile 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Makefile | 2 +- docs/local_area_calibration_setup.ipynb | 154 ++++++++++++------------ 2 files changed, 77 insertions(+), 79 deletions(-) diff --git a/Makefile b/Makefile index d096f1ef..4d5f5bef 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ database: python policyengine_us_data/db/etl_irs_soi.py python policyengine_us_data/db/validate_database.py -data: +data: download python policyengine_us_data/utils/uprating.py python policyengine_us_data/datasets/acs/acs.py python policyengine_us_data/datasets/cps/cps.py diff --git a/docs/local_area_calibration_setup.ipynb b/docs/local_area_calibration_setup.ipynb index c1ea246b..cdd1cc97 100644 --- a/docs/local_area_calibration_setup.ipynb +++ b/docs/local_area_calibration_setup.ipynb @@ -28,7 +28,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/baogorek/envs/sep/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + "/home/baogorek/envs/pe/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] }, @@ -66,7 +66,7 @@ "metadata": {}, "outputs": [], "source": [ - "db_path = STORAGE_FOLDER / \"policy_data.db\"\n", + "db_path = STORAGE_FOLDER / \"calibration\" / \"policy_data.db\"\n", "db_uri = f\"sqlite:///{db_path}\"\n", "dataset_path = str(STORAGE_FOLDER / \"stratified_extended_cps_2023.h5\")\n", "\n", @@ -148,7 +148,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "cell-7", "metadata": {}, "outputs": [ @@ -156,10 +156,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "X_sparse shape: (539, 256652)\n", + "X_sparse shape: (539, 256633)\n", " Rows (targets): 539\n", - " Columns (household × CD pairs): 256652\n", - " Non-zero entries: 67,668\n", + " Columns (household × CD pairs): 256633\n", + " Non-zero entries: 67,756\n", " Sparsity: 99.95%\n" ] } @@ -199,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "cell-9", "metadata": {}, "outputs": [ @@ -212,10 +212,10 @@ "MATRIX STRUCTURE BREAKDOWN\n", "================================================================================\n", "\n", - "Matrix dimensions: 539 rows x 256652 columns\n", + "Matrix dimensions: 539 rows x 256633 columns\n", " Rows = 539 targets\n", - " Columns = 13508 households x 19 CDs\n", - " = 13,508 x 19 = 256,652\n", + " Columns = 13507 households x 19 CDs\n", + " = 13,507 x 19 = 256,633\n", "\n", "--------------------------------------------------------------------------------\n", "COLUMN STRUCTURE (Households stacked by CD)\n", @@ -225,19 +225,19 @@ "\n", "First 5 CDs:\n", "cd_geoid start_col end_col n_households\n", - " 1501 0 13507 13508\n", - " 1502 13508 27015 13508\n", - " 201 27016 40523 13508\n", - " 3001 40524 54031 13508\n", - " 3002 54032 67539 13508\n", + " 1501 0 13506 13507\n", + " 1502 13507 27013 13507\n", + " 201 27014 40520 13507\n", + " 3001 40521 54027 13507\n", + " 3002 54028 67534 13507\n", "\n", "Last 5 CDs:\n", "cd_geoid start_col end_col n_households\n", - " 3710 189112 202619 13508\n", - " 3711 202620 216127 13508\n", - " 3712 216128 229635 13508\n", - " 3713 229636 243143 13508\n", - " 3714 243144 256651 13508\n", + " 3710 189098 202604 13507\n", + " 3711 202605 216111 13507\n", + " 3712 216112 229618 13507\n", + " 3713 229619 243125 13507\n", + " 3714 243126 256632 13507\n", "\n", "--------------------------------------------------------------------------------\n", "ROW STRUCTURE (Targets)\n", @@ -288,7 +288,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "cell-11", "metadata": {}, "outputs": [ @@ -320,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "7e75756b-a317-4800-bac5-e0fd6bc43b8c", "metadata": {}, "outputs": [ @@ -347,7 +347,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "c2be9721-ff11-4f78-ba0b-03407201dd53", "metadata": {}, "outputs": [ @@ -362,13 +362,13 @@ "3 110 1457.579956 23 0.000000\n", "4 140 1445.209961 23 0.000000\n", "... ... ... ... ...\n", - "13503 178916 0.000000 15 0.000000\n", - "13504 178918 0.000000 15 0.000000\n", - "13505 178926 0.000000 15 0.000000\n", - "13506 178929 0.000000 15 0.000000\n", - "13507 178945 0.000000 15 0.000000\n", + "13502 178916 0.000000 15 0.000000\n", + "13503 178918 0.000000 15 0.000000\n", + "13504 178926 0.000000 15 0.000000\n", + "13505 178935 0.000000 15 0.000000\n", + "13506 178945 0.000000 15 0.000000\n", "\n", - "[13508 rows x 4 columns]\n" + "[13507 rows x 4 columns]\n" ] } ], @@ -391,7 +391,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "cell-12", "metadata": {}, "outputs": [ @@ -404,7 +404,7 @@ "\n", "Evaluating the tracer.get_household_column_positions dictionary:\n", "\n", - "{'1501': 0, '1502': 13508, '201': 27016, '3001': 40524, '3002': 54032, '3701': 67540, '3702': 81048, '3703': 94556, '3704': 108064, '3705': 121572, '3706': 135080, '3707': 148588, '3708': 162096, '3709': 175604, '3710': 189112, '3711': 202620, '3712': 216128, '3713': 229636, '3714': 243144}\n" + "{'1501': 0, '1502': 13507, '201': 27014, '3001': 40521, '3002': 54028, '3701': 67535, '3702': 81042, '3703': 94549, '3704': 108056, '3705': 121563, '3706': 135070, '3707': 148577, '3708': 162084, '3709': 175591, '3710': 189098, '3711': 202605, '3712': 216112, '3713': 229619, '3714': 243126}\n" ] } ], @@ -428,7 +428,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "e05aaeab-3786-4ff0-a50b-34577065d2e0", "metadata": {}, "outputs": [ @@ -480,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "8cdc264c-8335-40eb-afd9-4c4d023ec303", "metadata": {}, "outputs": [ @@ -507,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "ac59b6f1-859f-4246-8a05-8cb26384c882", "metadata": {}, "outputs": [ @@ -538,7 +538,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "cell-19", "metadata": {}, "outputs": [ @@ -657,27 +657,27 @@ "Processing subset of 2 CDs: 3701, 201...\n", "Output path: calibration_output/results.h5\n", "\n", - "Original dataset has 13,508 households\n", + "Original dataset has 13,507 households\n", "Extracted weights for 2 CDs from full weight matrix\n", - "Total active household-CD pairs: 278\n", - "Total weight in W matrix: 282\n", + "Total active household-CD pairs: 284\n", + "Total weight in W matrix: 288\n", "Processing CD 201 (2/2)...\n", "\n", "Combining 2 CD DataFrames...\n", - "Total households across all CDs: 278\n", - "Combined DataFrame shape: (799, 166)\n", + "Total households across all CDs: 284\n", + "Combined DataFrame shape: (898, 167)\n", "\n", "Reindexing all entity IDs using 25k ranges per CD...\n", - " Created 278 unique households across 2 CDs\n", + " Created 284 unique households across 2 CDs\n", " Reindexing persons using 25k ranges...\n", " Reindexing tax units...\n", " Reindexing SPM units...\n", " Reindexing marital units...\n", - " Final persons: 799\n", - " Final households: 278\n", - " Final tax units: 410\n", - " Final SPM units: 287\n", - " Final marital units: 622\n", + " Final persons: 898\n", + " Final households: 284\n", + " Final tax units: 438\n", + " Final SPM units: 298\n", + " Final marital units: 693\n", "\n", "Weights in combined_df AFTER reindexing:\n", " HH weight sum: 0.00M\n", @@ -685,8 +685,8 @@ " Ratio: 1.00\n", "\n", "Overflow check:\n", - " Max person ID after reindexing: 5,125,399\n", - " Max person ID × 100: 512,539,900\n", + " Max person ID after reindexing: 5,125,419\n", + " Max person ID × 100: 512,541,900\n", " int32 max: 2,147,483,647\n", " ✓ No overflow risk!\n", "\n", @@ -694,18 +694,16 @@ "Building simulation from Dataset...\n", "\n", "Saving to calibration_output/results.h5...\n", - "Found 168 input variables (excluding calculated variables)\n", + "Found 165 input variables to save\n", "Variables saved: 163\n", - "Variables skipped: 3255\n", + "Variables skipped: 3491\n", "Sparse CD-stacked dataset saved successfully!\n", "Household mapping saved to calibration_output/mappings/results_household_mapping.csv\n", "\n", "Verifying saved file...\n", - " Final households: 278\n", - " Final persons: 799\n", - " Total population (from household weights): 282\n", - " Total population (from person weights): 803\n", - " Average persons per household: 2.85\n" + " Final households: 284\n", + " Final persons: 898\n", + " Total population (from household weights): 288\n" ] }, { @@ -791,26 +789,26 @@ "3 50003 201 \n", "4 50004 201 \n", ".. ... ... \n", - "273 125135 3701 \n", - "274 125136 3701 \n", - "275 125137 3701 \n", - "276 125138 3701 \n", - "277 125139 3701 \n", - "\n", - " county household_weight state_fips snap \n", - "0 ALEUTIANS_WEST_CENSUS_AREA_AK 3.5 2 342.480042 \n", - "1 BRISTOL_BAY_BOROUGH_AK 1.0 2 0.000000 \n", - "2 ALEUTIANS_EAST_BOROUGH_AK 1.0 2 0.000000 \n", - "3 NOME_CENSUS_AREA_AK 1.0 2 0.000000 \n", - "4 WRANGELL_CITY_AND_BOROUGH_AK 1.0 2 0.000000 \n", - ".. ... ... ... ... \n", - "273 WASHINGTON_COUNTY_NC 1.0 37 0.000000 \n", - "274 CURRITUCK_COUNTY_NC 1.0 37 0.000000 \n", - "275 LENOIR_COUNTY_NC 1.0 37 0.000000 \n", - "276 WAYNE_COUNTY_NC 1.0 37 0.000000 \n", - "277 NASH_COUNTY_NC 1.0 37 0.000000 \n", - "\n", - "[278 rows x 6 columns]\n" + "279 125125 3701 \n", + "280 125126 3701 \n", + "281 125127 3701 \n", + "282 125128 3701 \n", + "283 125129 3701 \n", + "\n", + " county household_weight state_fips snap \n", + "0 ALEUTIANS_WEST_CENSUS_AREA_AK 3.5 2 342.480042 \n", + "1 YUKON_KOYUKUK_CENSUS_AREA_AK 1.0 2 3433.199707 \n", + "2 ALEUTIANS_EAST_BOROUGH_AK 1.0 2 0.000000 \n", + "3 SITKA_CITY_AND_BOROUGH_AK 1.0 2 0.000000 \n", + "4 ANCHORAGE_MUNICIPALITY_AK 1.0 2 0.000000 \n", + ".. ... ... ... ... \n", + "279 LENOIR_COUNTY_NC 1.0 37 0.000000 \n", + "280 CHOWAN_COUNTY_NC 1.0 37 0.000000 \n", + "281 LENOIR_COUNTY_NC 1.0 37 0.000000 \n", + "282 WAYNE_COUNTY_NC 1.0 37 0.000000 \n", + "283 EDGECOMBE_COUNTY_NC 1.0 37 0.000000 \n", + "\n", + "[284 rows x 6 columns]\n" ] } ], @@ -945,7 +943,7 @@ " 342.480042\n", " \n", " \n", - " 138\n", + " 154\n", " 125000\n", " 3701\n", " VANCE_COUNTY_NC\n", @@ -960,11 +958,11 @@ "text/plain": [ " household_id congressional_district_geoid \\\n", "0 50000 201 \n", - "138 125000 3701 \n", + "154 125000 3701 \n", "\n", " county household_weight state_fips snap \n", "0 ALEUTIANS_WEST_CENSUS_AREA_AK 3.5 2 342.480042 \n", - "138 VANCE_COUNTY_NC 2.5 37 789.199951 " + "154 VANCE_COUNTY_NC 2.5 37 789.199951 " ] }, "execution_count": 21, From ac69e4ffb02745a0752d6e4669ddaf7c14684b32 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Mon, 29 Dec 2025 10:36:11 -0500 Subject: [PATCH 21/24] Fix test fixture path to use absolute path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use os.path.dirname(__file__) instead of relative path so tests work regardless of working directory. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../test_local_area_calibration/test_stacked_dataset_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py b/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py index d977392f..e96b1087 100644 --- a/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py +++ b/policyengine_us_data/tests/test_local_area_calibration/test_stacked_dataset_builder.py @@ -11,7 +11,7 @@ create_sparse_cd_stacked_dataset, ) -FIXTURE_PATH = "policyengine_us_data/tests/test_local_area_calibration/test_fixture_50hh.h5" +FIXTURE_PATH = os.path.join(os.path.dirname(__file__), "test_fixture_50hh.h5") TEST_CDS = ["3701", "201"] # NC-01 and AK at-large SEED = 42 From 0fd2c12ae929a92397a25e622ec6d0f6be8d67ac Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Mon, 29 Dec 2025 11:50:47 -0500 Subject: [PATCH 22/24] Trigger CI after runner restart From 1e5e0ab30ebd6987f65a2043c17ffa4487184f7d Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Mon, 29 Dec 2025 12:38:39 -0500 Subject: [PATCH 23/24] Add uv.lock to pin dependency versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add uv.lock file with all pinned dependencies - Update all workflows to use `uv sync --dev` instead of pip install - Add lock freshness check to PR workflow - Narrow Python version to >=3.12 (required by microimpute) This prevents stale cached packages on the self-hosted runner from causing test failures (e.g., missing spm_unit_tenure_type variable). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/code_changes.yaml | 2 +- .github/workflows/local_area_publish.yaml | 2 +- .github/workflows/pr_code_changes.yaml | 22 +- .github/workflows/reusable_test.yaml | 2 +- pyproject.toml | 3 +- uv.lock | 3184 +++++++++++++++++++++ 6 files changed, 3209 insertions(+), 6 deletions(-) create mode 100644 uv.lock diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 928fa3a7..aadf5c9d 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -37,7 +37,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 - name: Install package - run: uv pip install -e .[dev] --system + run: uv sync --dev - name: Build package run: python -m build - name: Publish a Python distribution to PyPI diff --git a/.github/workflows/local_area_publish.yaml b/.github/workflows/local_area_publish.yaml index 5a87d3f6..573475b2 100644 --- a/.github/workflows/local_area_publish.yaml +++ b/.github/workflows/local_area_publish.yaml @@ -43,7 +43,7 @@ jobs: service_account: "policyengine-research@policyengine-research.iam.gserviceaccount.com" - name: Install package - run: uv pip install -e .[dev] --system + run: uv sync --dev - name: Download checkpoint (if exists) continue-on-error: true diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index 6a375b04..d8f2e7bf 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -30,8 +30,28 @@ jobs: fi echo "✅ PR is from the correct repository" - Lint: + check-lock-freshness: + name: Check uv.lock freshness + runs-on: ubuntu-latest needs: check-fork + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Check lock file is up-to-date + run: | + uv lock --upgrade + git diff --exit-code uv.lock || { + echo "::error::uv.lock is outdated. Run 'uv lock --upgrade' and commit the changes." + exit 1 + } + + Lint: + needs: [check-fork, check-lock-freshness] uses: ./.github/workflows/reusable_lint.yaml SmokeTestForMultipleVersions: diff --git a/.github/workflows/reusable_test.yaml b/.github/workflows/reusable_test.yaml index c439be4e..8e0025e4 100644 --- a/.github/workflows/reusable_test.yaml +++ b/.github/workflows/reusable_test.yaml @@ -57,7 +57,7 @@ jobs: service_account: "policyengine-research@policyengine-research.iam.gserviceaccount.com" - name: Install package - run: uv pip install -e .[dev] --system + run: uv sync --dev - name: Download data inputs if: inputs.full_suite diff --git a/pyproject.toml b/pyproject.toml index ddf69089..eddb4b84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,8 @@ authors = [ {name = "PolicyEngine", email = "hello@policyengine.org"}, ] license = {file = "LICENSE"} -requires-python = ">=3.11, <3.14.0" +requires-python = ">=3.12, <3.14.0" classifiers = [ - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..b4382106 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3184 @@ +version = 1 +revision = 3 +requires-python = ">=3.12, <3.14.0" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "alembic" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + +[[package]] +name = "argparse" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4", size = 70508, upload-time = "2015-09-12T20:22:16.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314", size = 23000, upload-time = "2015-09-14T16:03:16.137Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "black" +version = "25.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" }, + { url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" }, + { url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" }, + { url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" }, + { url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" }, + { url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "blosc2" +version = "3.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "ndindex" }, + { name = "numexpr", marker = "platform_machine != 'wasm32'" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "py-cpuinfo", marker = "platform_machine != 'wasm32'" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/14/f5287028e013d16ab6dadc06b27fd5cb37fa9992c6fed4918ba8bb9889be/blosc2-3.12.2.tar.gz", hash = "sha256:a42f915c4b73e788bdc205c5473dcd8dd7a0290693408be471391d0ca65fe39f", size = 3974613, upload-time = "2025-12-04T11:43:31.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/48/7e146eb59d00deef7f4266205cf4384cdaebf897b3ad18a361db0762b54d/blosc2-3.12.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53e2c0729cd09c342ad113bf46990b7ca9803732dd89a0523a2f4889a29e2bc9", size = 3999740, upload-time = "2025-12-04T11:43:01.596Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5b/e635eea25ffa8365f8693082adeadf3ab12b823c0be0efe27b397d5af20b/blosc2-3.12.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0f69e50d127b039764cdcbceb2d7d58a0597c7ba51a18c62cefbcc3fc0c26cd", size = 3459066, upload-time = "2025-12-04T11:43:03.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/8b/b1cf8253ed3305c76d709be8dccf554e3f89ea4bae320db1ea913f385af3/blosc2-3.12.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9049b7d87a87ca77d78b9ac9e3714e0a42e23dc65ae92bd54ad6ffa74ef16b8b", size = 4358079, upload-time = "2025-12-04T11:43:04.569Z" }, + { url = "https://files.pythonhosted.org/packages/3a/47/b00b50be18b218ddda98e37cab173022544272940b2a39820d1504b4c246/blosc2-3.12.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd49b853e746923748f7cec6011b5dd8a9ebac647ac1625c362ef191fa453d3", size = 4494354, upload-time = "2025-12-04T11:43:06.252Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/b88f39271b44d4d34e2ff011eb7b1e9b2905d0095e0fa94ec1f84a5fb0cb/blosc2-3.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:598d40f1b91450bb2d8465f2819fc3bace017a42c5d9f2d25cd142eda0708efe", size = 2266229, upload-time = "2025-12-04T11:43:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/48/80/60a87aad4c4195ecf72aa471bbe220918c7dcf8964d939ed561dbc2377c1/blosc2-3.12.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:27b7772ed5e5a853a8bb2350cef2c7883c92256396c0eef499f419d87c91802b", size = 3999662, upload-time = "2025-12-04T11:43:08.715Z" }, + { url = "https://files.pythonhosted.org/packages/77/ba/f0dde80fc1e23828f9a69e8b73db0adb9d81eec1ac81b4b2dedaabfd28ff/blosc2-3.12.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f1d889a222b98d26b0031141685ec174b0fc9118f1e22c43dd0b65508c12970a", size = 3458834, upload-time = "2025-12-04T11:43:10.075Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d4/b8801ae11cbf5acfb1e55ce3e1206840449b94b61dbd912a3e4c3793da0a/blosc2-3.12.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e17c5f6ba010a33700586bb921ca72efd46223a22f3695dcecfabbb7ed452e58", size = 4357441, upload-time = "2025-12-04T11:43:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/aa/07/520849e62f3c62a6cad7c76559adceaba032ddb26c3d9e1da381bc18b5ea/blosc2-3.12.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e597b9c2bdd475159ee35684df1d6a4291cb5f3d7fb178734c81f033d17a9130", size = 4495409, upload-time = "2025-12-04T11:43:12.696Z" }, + { url = "https://files.pythonhosted.org/packages/69/e5/fd327ac868415958656d750f0ec8d63d94045053ba2e811c741134f83282/blosc2-3.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:fde3d9c9f6279b93cf6c62177e5c873add2cd625bb220bc96b4928e93c81bda0", size = 2267508, upload-time = "2025-12-04T11:43:14.26Z" }, +] + +[[package]] +name = "build" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/1c/23e33405a7c9eac261dff640926b8b5adaed6a6eb3e1767d441ed611d0c0/build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", size = 48544, upload-time = "2025-08-01T21:27:09.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731, upload-time = "2025-12-15T18:24:53.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551, upload-time = "2025-12-15T18:24:52.332Z" }, +] + +[[package]] +name = "census" +version = "0.8.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/cc/5161b96b309331e54e3acb06b67ac1d2a98f52cc9d0e27627abb527115f4/census-0.8.24.tar.gz", hash = "sha256:5c6b789652f9a3ae2eb5762367405ae7ca04be7e0f3416700ddc300fc9fe7768", size = 13048, upload-time = "2025-04-08T15:52:14.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/73/3868a695f082f379dce20f19b55451fef4c3f4337824f0991dc1a228301b/census-0.8.24-py3-none-any.whl", hash = "sha256:9ac2c2adca6a7c074d0e6551e1e3bd819724e5d309f0b0d72285247436b58089", size = 11401, upload-time = "2025-04-08T15:52:13.449Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "colorlog" +version = "6.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "datetime" +version = "6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/32/decbfd165e9985ba9d8c2d34a39afe5aeba2fc3fe390eb6e9ef1aab98fa8/datetime-6.0.tar.gz", hash = "sha256:c1514936d2f901e10c8e08d83bf04e6c9dbd7ca4f244da94fec980980a3bc4d5", size = 64167, upload-time = "2025-11-25T08:00:34.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/7a/ea0f3e3ea74be36fc7cf54f966cde732a3de72697983cdb5646b0a4dacde/datetime-6.0-py3-none-any.whl", hash = "sha256:d19988f0657a4e72c9438344157254a8dcad6aea8cd5ae70a5d1b5a75e5dc930", size = 52637, upload-time = "2025-11-25T08:00:33.077Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/75/9e12d4d42349b817cd545b89247696c67917aab907012ae5b64bbfea3199/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb", size = 1644590, upload-time = "2025-12-15T21:53:28.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/15/d762e5263d9e25b763b78be72dc084c7a32113a0bac119e2f7acae7700ed/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e", size = 2549995, upload-time = "2025-12-15T21:53:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/a7/88/f7d25c68b18873b7c53d7c156ca7a7ffd8e77073aa0eac170a9b679cf786/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de", size = 4309891, upload-time = "2025-12-15T21:53:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4f/a65e973aba3865794da65f71971dca01ae66666132c7b2647182d5be0c5f/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee", size = 5286355, upload-time = "2025-12-15T21:53:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3a/d3d8b48fec96e3d824e404bf428276fb8419dfa766f78f10b08da1cb2986/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d", size = 5328239, upload-time = "2025-12-15T21:53:48.868Z" }, + { url = "https://files.pythonhosted.org/packages/71/3d/388035a31a59c26f1ecc8d86af607d0c42e20ef80074147cd07b180c4349/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d", size = 2538859, upload-time = "2025-12-15T21:53:50.478Z" }, + { url = "https://files.pythonhosted.org/packages/4a/19/c93a0772d0962294f083dbdb113af1a7427bb632d36e5314297068f55db7/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606", size = 4292575, upload-time = "2025-12-15T21:53:51.821Z" }, + { url = "https://files.pythonhosted.org/packages/5c/56/09e48ab796b0a77e3d7dc250f95251832b8bf6838c9632f6100c98bdf426/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976", size = 5286209, upload-time = "2025-12-15T21:53:53.602Z" }, + { url = "https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73", size = 5328206, upload-time = "2025-12-15T21:53:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38", size = 5292321, upload-time = "2025-12-15T21:54:16.024Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "dpath" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/ce/e1fd64d36e4a5717bd5e6b2ad188f5eaa2e902fde871ea73a79875793fc9/dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e", size = 28266, upload-time = "2024-06-12T22:08:03.686Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/d1/8952806fbf9583004ab479d8f58a9496c3d35f6b6009ddd458bdd9978eaf/dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576", size = 17618, upload-time = "2024-06-12T22:08:01.881Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748, upload-time = "2025-12-03T15:23:42.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, +] + +[[package]] +name = "furo" +version = "2025.12.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "beautifulsoup4" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "sphinx-basic-ng" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/da/83d7043169ac2c8c7469f0e375610d78ae2160134bf1b80634c482fa079c/google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8", size = 176759, upload-time = "2025-10-28T21:34:51.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/d4/90197b416cb61cefd316964fd9e7bd8324bcbafabf40eef14a9f20b81974/google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c", size = 173706, upload-time = "2025-10-28T21:34:50.151Z" }, +] + +[[package]] +name = "google-auth" +version = "2.45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/00/3c794502a8b892c404b2dea5b3650eb21bfc7069612fbfd15c7f17c1cb0d/google_auth-2.45.0.tar.gz", hash = "sha256:90d3f41b6b72ea72dd9811e765699ee491ab24139f34ebf1ca2b9cc0c38708f3", size = 320708, upload-time = "2025-12-15T22:58:42.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/97/451d55e05487a5cd6279a01a7e34921858b16f7dc8aa38a2c684743cd2b3/google_auth-2.45.0-py2.py3-none-any.whl", hash = "sha256:82344e86dc00410ef5382d99be677c6043d72e502b625aa4f4afa0bdacca0f36", size = 233312, upload-time = "2025-12-15T22:58:40.777Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/03/ef0bc99d0e0faf4fdbe67ac445e18cdaa74824fd93cd069e7bb6548cb52d/google_cloud_core-2.5.0.tar.gz", hash = "sha256:7c1b7ef5c92311717bd05301aa1a91ffbc565673d3b0b4163a52d8413a186963", size = 36027, upload-time = "2025-10-29T23:17:39.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl", hash = "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", size = 29469, upload-time = "2025-10-29T23:17:38.548Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/8e/fab2de1a0ab7fdbd452eaae5a9a5c933d0911c26b04efa0c76ddfd921259/google_cloud_storage-3.7.0.tar.gz", hash = "sha256:9ce59c65f4d6e372effcecc0456680a8d73cef4f2dc9212a0704799cb3d69237", size = 17258914, upload-time = "2025-12-09T18:24:48.97Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/80/6e5c7c83cea15ed4dfc4843b9df9db0716bc551ac938f7b5dd18a72bd5e4/google_cloud_storage-3.7.0-py3-none-any.whl", hash = "sha256:469bc9540936e02f8a4bfd1619e9dca1e42dec48f95e4204d783b36476a15093", size = 303364, upload-time = "2025-12-09T18:24:47.343Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/d7/520b62a35b23038ff005e334dba3ffc75fcf583bee26723f1fd8fd4b6919/google_resumable_media-2.8.0.tar.gz", hash = "sha256:f1157ed8b46994d60a1bc432544db62352043113684d4e030ee02e77ebe9a1ae", size = 2163265, upload-time = "2025-11-17T15:38:06.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl", hash = "sha256:dd14a116af303845a8d932ddae161a26e86cc229645bc98b39f026f9b1717582", size = 81340, upload-time = "2025-11-17T15:38:05.594Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, + { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, + { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, + { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, + { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, + { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h5py" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, + { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, + { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, + { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, + { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "shellingham" }, + { name = "tqdm" }, + { name = "typer-slim" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/c8/9cd2fcb670ba0e708bfdf95a1177b34ca62de2d3821df0773bc30559af80/huggingface_hub-1.2.3.tar.gz", hash = "sha256:4ba57f17004fd27bb176a6b7107df579865d4cde015112db59184c51f5602ba7", size = 614605, upload-time = "2025-12-12T15:31:42.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/8d/7ca723a884d55751b70479b8710f06a317296b1fa1c1dec01d0420d13e43/huggingface_hub-1.2.3-py3-none-any.whl", hash = "sha256:c9b7a91a9eedaa2149cdc12bdd8f5a11780e10de1f1024718becf9e41e5a4642", size = 520953, upload-time = "2025-12-12T15:31:40.339Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "8.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + +[[package]] +name = "itables" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/b9/f6a50d98cd303e01604370a39301382a5cf36f2571ff98746d9dfd73f4d4/itables-2.6.2.tar.gz", hash = "sha256:3f73f3041074db6437fbd2f72e8ab5f24c9685707625c085ff71701d43e4c62f", size = 2360659, upload-time = "2025-12-26T17:55:29.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/8f/37c3cf033cbd6450150c7d7d7fe916f99c98d7c56f6271dbdeee46368c38/itables-2.6.2-py3-none-any.whl", hash = "sha256:b0ceaef1a7fe2878021049cef10c281d47ce6afcd5807c57d22addd61e5fab05", size = 2393605, upload-time = "2025-12-26T17:55:26.721Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jellyfish" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/14/fc5bdb637996df181e5c4fa3b15dcc27d33215e6c41753564ae453bdb40f/jellyfish-1.2.1.tar.gz", hash = "sha256:72d2fda61b23babe862018729be73c8b0dc12e3e6601f36f6e65d905e249f4db", size = 364417, upload-time = "2025-10-11T19:36:37.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/52/4112537334f1b21ead968a663f0aeb8a5774f42f9c92ded69bad21db1c5e/jellyfish-1.2.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:32a85b752cb51463face13e2b1797cfa617cd7fb7073f15feaa4020a86a346ce", size = 323225, upload-time = "2025-10-11T19:35:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0d/c54aa2476e5e63673910720b75f3b15e2484687fff9a457a84861f3fa898/jellyfish-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:675ab43840488944899ca87f02d4813c1e32107e56afaba7489705a70214e8aa", size = 317839, upload-time = "2025-10-11T19:35:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/51/3f/a81347d705150a69e446cabcbe8f223ad990164dffd3e6f8178ed44cf198/jellyfish-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c888f624d03e55e501bc438906505c79fb307d8da37a6dda18dd1ac2e6d5ea9c", size = 353337, upload-time = "2025-10-11T19:35:20.651Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3a/b655e72b852f6c304a2bc12091485f71e58e8c6374a15c8f21a1f0e1b9cd/jellyfish-1.2.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2b56a1fd2c5126c4a3362ec4470291cdd3c7daa22f583da67e75e30dc425ce6", size = 362632, upload-time = "2025-10-11T19:35:21.624Z" }, + { url = "https://files.pythonhosted.org/packages/4e/be/f9f9a0b7ba48c994e0573d718e39bde713572cfb11f967d97328420a7aef/jellyfish-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a3ccff843822e7f3ad6f91662488a3630724c8587976bce114f3c7238e8ffa1", size = 360514, upload-time = "2025-10-11T19:35:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b6/960e556e155f65438c1b70d50f745ceb2989de8255a769ccaad26bf94a3f/jellyfish-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10da696747e2de0336180fd5ba77ef769a7c80f9743123545f7fc0251efbbcec", size = 533973, upload-time = "2025-10-11T19:35:24.077Z" }, + { url = "https://files.pythonhosted.org/packages/24/63/f5b5fb00c0df70387f699535c38190a97f30b79c2e7d4afb97794f838875/jellyfish-1.2.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c3c18f13175a9c90f3abd8805720b0eb3e10eca1d5d4e0cf57722b2a62d62016", size = 553863, upload-time = "2025-10-11T19:35:25.64Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/4de6626b6045884ed27995e170bacd09239b19549e25d95492cde10ea052/jellyfish-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0368596e176bf548b3be2979ff33e274fb6d5e13b2cebe85137b8b698b002a85", size = 523629, upload-time = "2025-10-11T19:35:26.732Z" }, + { url = "https://files.pythonhosted.org/packages/73/d6/8593e08568438b207f91b2fba2f6c879abc85dc450c0ad599a4e81dd9f07/jellyfish-1.2.1-cp312-cp312-win32.whl", hash = "sha256:451ddf4094e108e33d3b86d7817a7e20a2c5e6812d08c34ee22f6a595f38dcca", size = 209179, upload-time = "2025-10-11T19:35:27.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ff/ae991a96e8a370f41bbd91dbabdc94b404a164b0ab268388f43c2ab10d45/jellyfish-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:15318c13070fe6d9caeb7e10f9cdf89ff47c9d20f05a9a2c0d3b5cb8062a7033", size = 213630, upload-time = "2025-10-11T19:35:28.978Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e6/75feeda1c3634525296aa56265db151f896005b139e177f8b1a285546a1f/jellyfish-1.2.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4b3e3223aaad74e18aacc74775e01815e68af810258ceea6fa6a81b19f384312", size = 322958, upload-time = "2025-10-11T19:35:29.906Z" }, + { url = "https://files.pythonhosted.org/packages/0e/66/4b92bb55b545ebefbf085e45cbcda576d2a2a3dc48fd61dae469c27e73a6/jellyfish-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e967e67058b78189d2b20a9586c7720a05ec4a580d6a98c796cd5cd2b7b11303", size = 317859, upload-time = "2025-10-11T19:35:31.312Z" }, + { url = "https://files.pythonhosted.org/packages/fe/8e/9d0055f921c884605bf22a96e376b016993928126e8a4c7fd8698260fb4e/jellyfish-1.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32581c50b34a09889b2d96796170e53da313a1e7fde32be63c82e50e7e791e3c", size = 353222, upload-time = "2025-10-11T19:35:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d2/deca58a62e57f7e2b2172ab39f522831279ee08ec0943fc0d0e33cd6e6f9/jellyfish-1.2.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07b022412ebece96759006cb015d46b8218d7f896d8b327c6bbee784ddf38ed9", size = 362392, upload-time = "2025-10-11T19:35:33.305Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/9a7f62d367f5a862950ce3598188fe0e22e11d1f5d6eaad6eda5adc354b0/jellyfish-1.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a49eb817eaa6591f43a31e5c93d79904de62537f029907ef88c050d781a638", size = 360358, upload-time = "2025-10-11T19:35:34.585Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e5/6b44a1058df3dfa3dd1174c9f86685c78f780d0b68851a057075aea14587/jellyfish-1.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e1b990fb15985571616f7f40a12d6fa062897b19fb5359b6dec3cd811d802c24", size = 533945, upload-time = "2025-10-11T19:35:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/50/4c/2397f43ad2692a1052299607838b41a4c2dd5707fde4ce459d686e763eb1/jellyfish-1.2.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:dd895cf63fac0a9f11b524fff810d9a6081dcf3c518b34172ac8684eb504dd43", size = 553707, upload-time = "2025-10-11T19:35:36.926Z" }, + { url = "https://files.pythonhosted.org/packages/de/aa/dc7cf053c8c40035791de1dc2f45b1f57772a14b0dc53318720e87073831/jellyfish-1.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6d2bac5982d7a08759ea487bfa00149e6aa8a3be7cd43c4ed1be1e3505425c69", size = 523323, upload-time = "2025-10-11T19:35:37.981Z" }, + { url = "https://files.pythonhosted.org/packages/2b/1a/610c7f1f7777646322f489b5ed1e4631370c9fa4fb40a8246af71b496b6d/jellyfish-1.2.1-cp313-cp313-win32.whl", hash = "sha256:509355ebedec69a8bf0cc113a6bf9c01820d12fe2eea44f47dfa809faf2d5463", size = 209143, upload-time = "2025-10-11T19:35:39.276Z" }, + { url = "https://files.pythonhosted.org/packages/80/9a/6102b23b03a6df779fee76c979c0eb819b300c83b468900df78bb574b944/jellyfish-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:9c747ae5c0fb4bd519f6abbfe4bd704b2f1c63fd4dd3dbb8d8864478974e1571", size = 213466, upload-time = "2025-10-11T19:35:40.24Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "jsonpickle" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/a6/d07afcfdef402900229bcca795f80506b207af13a838d4d99ad45abf530c/jsonpickle-4.1.1.tar.gz", hash = "sha256:f86e18f13e2b96c1c1eede0b7b90095bbb61d99fedc14813c44dc2f361dbbae1", size = 316885, upload-time = "2025-06-02T20:36:11.57Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/73/04df8a6fa66d43a9fd45c30f283cc4afff17da671886e451d52af60bdc7e/jsonpickle-4.1.1-py3-none-any.whl", hash = "sha256:bb141da6057898aa2438ff268362b126826c812a1721e31cf08a6e142910dc91", size = 47125, upload-time = "2025-06-02T20:36:08.647Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-book" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "jupyter-core" }, + { name = "jupyter-server" }, + { name = "nodeenv" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/ef/618a3080c9c2f76f6d17212b7954db448d7f92da6459450a6acce1e0d596/jupyter_book-2.1.0.tar.gz", hash = "sha256:f42b6f264e6e270b07362f4430ef671edccbb1be36cd0872afd231c642a4ba7e", size = 14440795, upload-time = "2025-12-05T13:16:57.496Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/0a/066cacdbe5fa68a39df245cd91db2a77e7485cb4c94d8c48cc3e8ed02e45/jupyter_book-2.1.0-py3-none-any.whl", hash = "sha256:cdf54323e0b5c0e1d9a6972742ac2dbfea9f7617e20f58113dc25cc85b7ac4d9", size = 2599900, upload-time = "2025-12-05T13:16:54.292Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/27/d10de45e8ad4ce872372c4a3a37b7b35b6b064f6f023a5c14ffcced4d59d/jupyter_client-8.7.0.tar.gz", hash = "sha256:3357212d9cbe01209e59190f67a3a7e1f387a4f4e88d1e0433ad84d7b262531d", size = 344691, upload-time = "2025-12-09T18:37:01.953Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl", hash = "sha256:3671a94fd25e62f5f2f554f5e95389c2294d89822378a5f2dd24353e1494a9e0", size = 106215, upload-time = "2025-12-09T18:37:00.024Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "microdf-python" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/e9/7f77ee19b2d3dcdb561ceaeb915ef6a944eb315412d0e8065c12817b7ff4/microdf_python-1.1.1.tar.gz", hash = "sha256:97dd0cb8562d98a32bbd35861719ae565544143e2ad8a462d6f6261058d9c469", size = 17290, upload-time = "2025-12-01T14:13:57.131Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/53/a6b3474d1f46bbab9ffa8a6d5727662e440e7689337316c90f4c3b8c038f/microdf_python-1.1.1-py3-none-any.whl", hash = "sha256:f06ec231a58dcf7ab1c6fa6a8cf25632a34ebec0f67b9067c7c0364d4c0aeb4f", size = 18096, upload-time = "2025-12-01T14:13:56.056Z" }, +] + +[[package]] +name = "microimpute" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "optuna" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "quantile-forest" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "statsmodels" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/f1/b3e407ddadea69198b36f87b855416684d99631a1f62fb952ceb820f755c/microimpute-1.12.0.tar.gz", hash = "sha256:f8554b2f40d0d11b079860e7b32af04acb7910a8632dc5a6a8c469990c4aa225", size = 125271, upload-time = "2025-12-11T14:05:13.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/5f/6fb8a1058c6e06670f6cea56b49300cf169e685e254ff2455a97afc3f64b/microimpute-1.12.0-py3-none-any.whl", hash = "sha256:76433c4927a2140ab217e1da503b1e5c2fff03c4b6dfd940d8d7d5ccfc2df9fd", size = 108702, upload-time = "2025-12-11T14:05:12.005Z" }, +] + +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "mystmd" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/b1/b129b0efe29c73b0e9bdaf556bd53d2371d51c9e922869b1b9c542c74ee2/mystmd-1.7.1.tar.gz", hash = "sha256:ae9a49a7c25aa9a79bc4ef921ffcf20a163f082e76592b296666ba6a6f9a4133", size = 2778812, upload-time = "2025-12-10T23:37:34.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/77/7abd13ca8304ec1d8832c23455107f4dc44c4684e56e1578365065739504/mystmd-1.7.1-py3-none-any.whl", hash = "sha256:c85019ac9d9e238d2ddd314f295e07a9950c671f759b497c85b6d260444628d4", size = 2810038, upload-time = "2025-12-10T23:37:33.305Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "ndindex" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/92/4b9d2f4e0f3eabcfc7b02b48261f6e5ad36a3e2c1bbdcc4e3b7b6c768fa6/ndindex-1.10.1.tar.gz", hash = "sha256:0f6113c1f031248f8818cbee1aa92aa3c9472b7701debcce9fddebcd2f610f11", size = 271395, upload-time = "2025-11-19T20:40:08.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/90/774ddd08b2a1b41faa56da111f0fbfeb4f17ee537214c938ef41d61af949/ndindex-1.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87f83e8c35a7f49a68cd3a3054c406e6c22f8c1315f3905f7a778c657669187e", size = 177348, upload-time = "2025-11-19T20:38:41.768Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ee/a423e857f5b45da3adc8ddbcfbfd4a0e9a047edce3915d3e3d6e189b6bd9/ndindex-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf9e05986b2eb8c5993bce0f911d6cedd15bda30b5e35dd354b1ad1f4cc3599d", size = 176561, upload-time = "2025-11-19T20:38:43.06Z" }, + { url = "https://files.pythonhosted.org/packages/1f/40/139b6b050ba2b2a0bb40e0381a352b1eb6551302dcb8f86fb4c97dd34e92/ndindex-1.10.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:046c1e88d46b2bd2fd3483e06d27b4e85132b55bc693f2fca2db0bb56eea1e78", size = 542901, upload-time = "2025-11-19T20:38:44.43Z" }, + { url = "https://files.pythonhosted.org/packages/27/ae/defd665dbbeb2fffa077491365ed160acaec49274ce8d4b979f55db71f18/ndindex-1.10.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03cf1e6cdac876bd8fc92d3b65bb223496b1581d10eab3ba113f7c195121a959", size = 546875, upload-time = "2025-11-19T20:38:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/59/43/6d54d48e8eaee25cdab70d3e4c4f579ddb0255e4f1660040d5ad55e029c6/ndindex-1.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:752e78a5e87911ded117c57a7246596f26c9c6da066de3c2b533b3db694949bb", size = 1510036, upload-time = "2025-11-19T20:38:47.444Z" }, + { url = "https://files.pythonhosted.org/packages/09/61/e28ba3b98eacd18193176526526b34d7d70d2a6f9fd2b4d8309ab5692678/ndindex-1.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9dd58d91220b1c1fe516324bfcf4114566c98e84b1cbbe416abe345c75bd557", size = 1571849, upload-time = "2025-11-19T20:38:48.951Z" }, + { url = "https://files.pythonhosted.org/packages/8f/63/83fff78a3712cb9f478dd84a19ec389acf6f8c7b01dc347a65ae74e6123d/ndindex-1.10.1-cp312-cp312-win32.whl", hash = "sha256:3b0d9ce2c8488444499ab6d40e92e09867bf4413f5cf04c01635de923f44aa67", size = 149792, upload-time = "2025-11-19T20:38:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/52/fd/a5e3c8c043d0dddea6cd4567bfaea568f022ac197301882b3d85d9c1e9b3/ndindex-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c026dbbf2455d97ce6456d8a50b349aee8fefa11027d020638c89e9be2c9c4c", size = 158164, upload-time = "2025-11-19T20:38:52.242Z" }, + { url = "https://files.pythonhosted.org/packages/60/ea/03676266cb38cc671679a9d258cc59bfc58c69726db87b0d6eeafb308895/ndindex-1.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:157b5c34a1b779f5d27b790d9bd7e7b156d284e76be83c591a3ba003984f4956", size = 176323, upload-time = "2025-11-19T20:38:53.528Z" }, + { url = "https://files.pythonhosted.org/packages/89/f4/2d350439031b108b0bb8897cad315390c5ad88c14d87419a54c2ffa95c80/ndindex-1.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f99b3e89220da3244d03c9c5473669c7107d361c129fd9b064622744dee1ce15", size = 175584, upload-time = "2025-11-19T20:38:57.968Z" }, + { url = "https://files.pythonhosted.org/packages/77/34/a51b7c6f7159718a6a0a694fc1058b94d793c416d9a4fd649f1924cce5f8/ndindex-1.10.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6928e47fb008903f2e41309b7ff1e59b16abbcd59e2e945454571c28b2433c9e", size = 524127, upload-time = "2025-11-19T20:38:59.412Z" }, + { url = "https://files.pythonhosted.org/packages/21/91/d8f19f0b8fc9c5585b50fda44c05415da0bdc5fa9c9c69011015dac27880/ndindex-1.10.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69a2cb1ac7be955c3c77f1def83f410775a81525c9ce2d4c0a3f2a61589ed47", size = 528213, upload-time = "2025-11-19T20:39:00.882Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a9/77d9d037e871a3faa8579b354ca2dd09cc5bbf3e085d9e3c67f786d55ee3/ndindex-1.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cb76e0f3f235d8b1c768b17e771de48775d281713795c3aa045e8114ad61bdda", size = 1492172, upload-time = "2025-11-19T20:39:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/ac/29/ad13676fc9312e0aa1a80a7c04bcb0b502b877ed4956136117ad663eced0/ndindex-1.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7da34a78410c14341d5fff73be5ce924bd36500bf7f640fc59b8607d3a0df95e", size = 1552614, upload-time = "2025-11-19T20:39:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/e6e6fd81423810c07ae623c4d36e099f42a812994977e8e3bfa182c02472/ndindex-1.10.1-cp313-cp313-win32.whl", hash = "sha256:9599fcb7411ffe601c367f0a5d4bc0ed588e3e7d9dc7604bdb32c8f669456b9e", size = 149330, upload-time = "2025-11-19T20:39:05.727Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d3/830a20626e2ec0e31a926be90e67068a029930f99e6cfebf2f9768e7b7b1/ndindex-1.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:ef3ef22390a892d16286505083ee5b326317b21c255a0c7f744b1290a0b964a6", size = 157309, upload-time = "2025-11-19T20:39:07.394Z" }, + { url = "https://files.pythonhosted.org/packages/4a/73/3bdeecd1f6ec0ad81478a53d96da4ba9be74ed297c95f2b4fbe2b80843e1/ndindex-1.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:72af787dcee3661f36fff9d144d989aacefe32e2c8b51ceef9babd46afb93a18", size = 181022, upload-time = "2025-11-19T20:39:10.487Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b1/0d97ba134b5aa71b5ed638fac193a7ec4d987e091e2f4e4162ebdaacbda1/ndindex-1.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa60637dfae1ee3fc057e420a52cc4ace38cf2c0d1a0451af2a3cba84d281842", size = 181289, upload-time = "2025-11-19T20:39:11.793Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d7/1df02df24880ce3f3c8137b6f3ca5a901a58d9079dcfd8c818419277ff87/ndindex-1.10.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ebdba2fade3f6916fe21fd49e2a0935af4f58c56100a60f3f2eb26e20baee7", size = 632517, upload-time = "2025-11-19T20:39:13.259Z" }, + { url = "https://files.pythonhosted.org/packages/34/96/b509c2b14e9b10710fe6ab6ba8bda1ee6ce36ab16397ff2f5bbb33bbbba3/ndindex-1.10.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:346a4bf09f5771548665c8206e81daadb6b9925d409746e709894bdd98adc701", size = 616179, upload-time = "2025-11-19T20:39:14.757Z" }, + { url = "https://files.pythonhosted.org/packages/38/e3/f89d60cf351c33a484bf1a4546a5dee6f4e7a6a973613ffa12bd316b14ad/ndindex-1.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:23d35696f802548143b5cc199bf2f171efb0061aa7934959251dd3bae56d038c", size = 1588373, upload-time = "2025-11-19T20:39:16.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/19/002fc1e6a4abeef8d92e9aa2e43aea4d462f6b170090f7752ea8887f4897/ndindex-1.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a91e1a0398120233d5c3b23ccb2d4b78e970d66136f1a7221fa9a53873c3d5c5", size = 1636436, upload-time = "2025-11-19T20:39:18.266Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/28b1ad78c787ac8fafd6e26419a80366617784b1779e3857fa687492f6bc/ndindex-1.10.1-cp313-cp313t-win32.whl", hash = "sha256:78bfe25941d2dac406391ddd9baf0b0fce163807b98ecc2c47a3030ee8466319", size = 158780, upload-time = "2025-11-19T20:39:20.454Z" }, + { url = "https://files.pythonhosted.org/packages/d0/56/b81060607a19865bb8be8d705b1b3e8aefb8747c0fbd383e38b4cae4bd71/ndindex-1.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:08bfdc1f7a0b408d15b3ce61d141ebbebdb47a25341967e425e104c5bd512a5c", size = 167485, upload-time = "2025-11-19T20:39:21.733Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "numexpr" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/2f/fdba158c9dbe5caca9c3eca3eaffffb251f2fb8674bf8e2d0aed5f38d319/numexpr-2.14.1.tar.gz", hash = "sha256:4be00b1086c7b7a5c32e31558122b7b80243fe098579b170967da83f3152b48b", size = 119400, upload-time = "2025-10-13T16:17:27.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/20/c473fc04a371f5e2f8c5749e04505c13e7a8ede27c09e9f099b2ad6f43d6/numexpr-2.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ebae0ab18c799b0e6b8c5a8d11e1fa3848eb4011271d99848b297468a39430", size = 162790, upload-time = "2025-10-13T16:16:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/45/93/b6760dd1904c2a498e5f43d1bb436f59383c3ddea3815f1461dfaa259373/numexpr-2.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47041f2f7b9e69498fb311af672ba914a60e6e6d804011caacb17d66f639e659", size = 152196, upload-time = "2025-10-13T16:16:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/72/94/cc921e35593b820521e464cbbeaf8212bbdb07f16dc79fe283168df38195/numexpr-2.14.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d686dfb2c1382d9e6e0ee0b7647f943c1886dba3adbf606c625479f35f1956c1", size = 452468, upload-time = "2025-10-13T16:13:29.531Z" }, + { url = "https://files.pythonhosted.org/packages/d9/43/560e9ba23c02c904b5934496486d061bcb14cd3ebba2e3cf0e2dccb6c22b/numexpr-2.14.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee6d4fbbbc368e6cdd0772734d6249128d957b3b8ad47a100789009f4de7083", size = 443631, upload-time = "2025-10-13T16:15:02.473Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6c/78f83b6219f61c2c22d71ab6e6c2d4e5d7381334c6c29b77204e59edb039/numexpr-2.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3a2839efa25f3c8d4133252ea7342d8f81226c7c4dda81f97a57e090b9d87a48", size = 1417670, upload-time = "2025-10-13T16:13:33.464Z" }, + { url = "https://files.pythonhosted.org/packages/0e/bb/1ccc9dcaf46281568ce769888bf16294c40e98a5158e4b16c241de31d0d3/numexpr-2.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9f9137f1351b310436662b5dc6f4082a245efa8950c3b0d9008028df92fefb9b", size = 1466212, upload-time = "2025-10-13T16:15:12.828Z" }, + { url = "https://files.pythonhosted.org/packages/31/9f/203d82b9e39dadd91d64bca55b3c8ca432e981b822468dcef41a4418626b/numexpr-2.14.1-cp312-cp312-win32.whl", hash = "sha256:36f8d5c1bd1355df93b43d766790f9046cccfc1e32b7c6163f75bcde682cda07", size = 166996, upload-time = "2025-10-13T16:17:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/ffe750b5452eb66de788c34e7d21ec6d886abb4d7c43ad1dc88ceb3d998f/numexpr-2.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:fdd886f4b7dbaf167633ee396478f0d0aa58ea2f9e7ccc3c6431019623e8d68f", size = 160187, upload-time = "2025-10-13T16:17:11.974Z" }, + { url = "https://files.pythonhosted.org/packages/73/b4/9f6d637fd79df42be1be29ee7ba1f050fab63b7182cb922a0e08adc12320/numexpr-2.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09078ba73cffe94745abfbcc2d81ab8b4b4e9d7bfbbde6cac2ee5dbf38eee222", size = 162794, upload-time = "2025-10-13T16:16:38.291Z" }, + { url = "https://files.pythonhosted.org/packages/35/ae/d58558d8043de0c49f385ea2fa789e3cfe4d436c96be80200c5292f45f15/numexpr-2.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dce0b5a0447baa7b44bc218ec2d7dcd175b8eee6083605293349c0c1d9b82fb6", size = 152203, upload-time = "2025-10-13T16:16:39.907Z" }, + { url = "https://files.pythonhosted.org/packages/13/65/72b065f9c75baf8f474fd5d2b768350935989d4917db1c6c75b866d4067c/numexpr-2.14.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:06855053de7a3a8425429bd996e8ae3c50b57637ad3e757e0fa0602a7874be30", size = 455860, upload-time = "2025-10-13T16:13:35.811Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f9/c9457652dfe28e2eb898372da2fe786c6db81af9540c0f853ee04a0699cc/numexpr-2.14.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f9366d23a2e991fd5a8b5e61a17558f028ba86158a4552f8f239b005cdf83c", size = 446574, upload-time = "2025-10-13T16:15:17.367Z" }, + { url = "https://files.pythonhosted.org/packages/b6/99/8d3879c4d67d3db5560cf2de65ce1778b80b75f6fa415eb5c3e7bd37ba27/numexpr-2.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c5f1b1605695778896534dfc6e130d54a65cd52be7ed2cd0cfee3981fd676bf5", size = 1417306, upload-time = "2025-10-13T16:13:42.813Z" }, + { url = "https://files.pythonhosted.org/packages/ea/05/6bddac9f18598ba94281e27a6943093f7d0976544b0cb5d92272c64719bd/numexpr-2.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a4ba71db47ea99c659d88ee6233fa77b6dc83392f1d324e0c90ddf617ae3f421", size = 1466145, upload-time = "2025-10-13T16:15:27.464Z" }, + { url = "https://files.pythonhosted.org/packages/24/5d/cbeb67aca0c5a76ead13df7e8bd8dd5e0d49145f90da697ba1d9f07005b0/numexpr-2.14.1-cp313-cp313-win32.whl", hash = "sha256:638dce8320f4a1483d5ca4fda69f60a70ed7e66be6e68bc23fb9f1a6b78a9e3b", size = 166996, upload-time = "2025-10-13T16:17:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/cc/23/9281bceaeb282cead95f0aa5f7f222ffc895670ea689cc1398355f6e3001/numexpr-2.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fdcd4735121658a313f878fd31136d1bfc6a5b913219e7274e9fca9f8dac3bb", size = 160189, upload-time = "2025-10-13T16:17:15.417Z" }, + { url = "https://files.pythonhosted.org/packages/f3/76/7aac965fd93a56803cbe502aee2adcad667253ae34b0badf6c5af7908b6c/numexpr-2.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:557887ad7f5d3c2a40fd7310e50597045a68e66b20a77b3f44d7bc7608523b4b", size = 163524, upload-time = "2025-10-13T16:16:42.213Z" }, + { url = "https://files.pythonhosted.org/packages/58/65/79d592d5e63fbfab3b59a60c386853d9186a44a3fa3c87ba26bdc25b6195/numexpr-2.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:af111c8fe6fc55d15e4c7cab11920fc50740d913636d486545b080192cd0ad73", size = 152919, upload-time = "2025-10-13T16:16:44.229Z" }, + { url = "https://files.pythonhosted.org/packages/84/78/3c8335f713d4aeb99fa758d7c62f0be1482d4947ce5b508e2052bb7aeee9/numexpr-2.14.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33265294376e7e2ae4d264d75b798a915d2acf37b9dd2b9405e8b04f84d05cfc", size = 465972, upload-time = "2025-10-13T16:13:45.061Z" }, + { url = "https://files.pythonhosted.org/packages/35/81/9ee5f69b811e8f18746c12d6f71848617684edd3161927f95eee7a305631/numexpr-2.14.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83647d846d3eeeb9a9255311236135286728b398d0d41d35dedb532dca807fe9", size = 456953, upload-time = "2025-10-13T16:15:31.186Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/9b8bc6e294d85cbb54a634e47b833e9f3276a8bdf7ce92aa808718a0212d/numexpr-2.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6e575fd3ad41ddf3355d0c7ef6bd0168619dc1779a98fe46693cad5e95d25e6e", size = 1426199, upload-time = "2025-10-13T16:13:48.231Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ce/0d4fcd31ab49319740d934fba1734d7dad13aa485532ca754e555ca16c8b/numexpr-2.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:67ea4771029ce818573b1998f5ca416bd255156feea017841b86176a938f7d19", size = 1474214, upload-time = "2025-10-13T16:15:38.893Z" }, + { url = "https://files.pythonhosted.org/packages/b7/47/b2a93cbdb3ba4e009728ad1b9ef1550e2655ea2c86958ebaf03b9615f275/numexpr-2.14.1-cp313-cp313t-win32.whl", hash = "sha256:15015d47d3d1487072d58c0e7682ef2eb608321e14099c39d52e2dd689483611", size = 167676, upload-time = "2025-10-13T16:17:17.351Z" }, + { url = "https://files.pythonhosted.org/packages/86/99/ee3accc589ed032eea68e12172515ed96a5568534c213ad109e1f4411df1/numexpr-2.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:94c711f6d8f17dfb4606842b403699603aa591ab9f6bf23038b488ea9cfb0f09", size = 161096, upload-time = "2025-10-13T16:17:19.174Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" }, + { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" }, + { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" }, + { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" }, + { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" }, + { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0d/853fd96372eda07c824d24adf02e8bc92bb3731b43a9b2a39161c3667cc4/numpy-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a152d86a3ae00ba5f47b3acf3b827509fd0b6cb7d3259665e63dafbad22a75ea", size = 16649088, upload-time = "2025-12-20T16:16:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/cc636f1f2a9f585434e20a3e6e63422f70bfe4f7f6698e941db52ea1ac9a/numpy-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39b19251dec4de8ff8496cd0806cbe27bf0684f765abb1f4809554de93785f2d", size = 12364065, upload-time = "2025-12-20T16:16:33.491Z" }, + { url = "https://files.pythonhosted.org/packages/ed/69/0b78f37ca3690969beee54103ce5f6021709134e8020767e93ba691a72f1/numpy-2.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:009bd0ea12d3c784b6639a8457537016ce5172109e585338e11334f6a7bb88ee", size = 5192640, upload-time = "2025-12-20T16:16:35.636Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2a/08569f8252abf590294dbb09a430543ec8f8cc710383abfb3e75cc73aeda/numpy-2.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5fe44e277225fd3dff6882d86d3d447205d43532c3627313d17e754fb3905a0e", size = 6541556, upload-time = "2025-12-20T16:16:37.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/e9/a949885a4e177493d61519377952186b6cbfdf1d6002764c664ba28349b5/numpy-2.4.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f935c4493eda9069851058fa0d9e39dbf6286be690066509305e52912714dbb2", size = 14396562, upload-time = "2025-12-20T16:16:38.953Z" }, + { url = "https://files.pythonhosted.org/packages/99/98/9d4ad53b0e9ef901c2ef1d550d2136f5ac42d3fd2988390a6def32e23e48/numpy-2.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cfa5f29a695cb7438965e6c3e8d06e0416060cf0d709c1b1c1653a939bf5c2a", size = 16351719, upload-time = "2025-12-20T16:16:41.503Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/5f3711a38341d6e8dd619f6353251a0cdd07f3d6d101a8fd46f4ef87f895/numpy-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba0cb30acd3ef11c94dc27fbfba68940652492bc107075e7ffe23057f9425681", size = 16176053, upload-time = "2025-12-20T16:16:44.552Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5b/2a3753dc43916501b4183532e7ace862e13211042bceafa253afb5c71272/numpy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60e8c196cd82cbbd4f130b5290007e13e6de3eca79f0d4d38014769d96a7c475", size = 18277859, upload-time = "2025-12-20T16:16:47.174Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/a18bcdd07a941db3076ef489d036ab16d2bfc2eae0cf27e5a26e29189434/numpy-2.4.0-cp313-cp313-win32.whl", hash = "sha256:5f48cb3e88fbc294dc90e215d86fbaf1c852c63dbdb6c3a3e63f45c4b57f7344", size = 5953849, upload-time = "2025-12-20T16:16:49.554Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f1/719010ff8061da6e8a26e1980cf090412d4f5f8060b31f0c45d77dd67a01/numpy-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:a899699294f28f7be8992853c0c60741f16ff199205e2e6cdca155762cbaa59d", size = 12302840, upload-time = "2025-12-20T16:16:51.227Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/b3d259083ed8b4d335270c76966cb6cf14a5d1b69e1a608994ac57a659e6/numpy-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9198f447e1dc5647d07c9a6bbe2063cc0132728cc7175b39dbc796da5b54920d", size = 10308509, upload-time = "2025-12-20T16:16:53.313Z" }, + { url = "https://files.pythonhosted.org/packages/31/01/95edcffd1bb6c0633df4e808130545c4f07383ab629ac7e316fb44fff677/numpy-2.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74623f2ab5cc3f7c886add4f735d1031a1d2be4a4ae63c0546cfd74e7a31ddf6", size = 12491815, upload-time = "2025-12-20T16:16:55.496Z" }, + { url = "https://files.pythonhosted.org/packages/59/ea/5644b8baa92cc1c7163b4b4458c8679852733fa74ca49c942cfa82ded4e0/numpy-2.4.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0804a8e4ab070d1d35496e65ffd3cf8114c136a2b81f61dfab0de4b218aacfd5", size = 5320321, upload-time = "2025-12-20T16:16:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/4e/e10938106d70bc21319bd6a86ae726da37edc802ce35a3a71ecdf1fdfe7f/numpy-2.4.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:02a2038eb27f9443a8b266a66911e926566b5a6ffd1a689b588f7f35b81e7dc3", size = 6641635, upload-time = "2025-12-20T16:16:59.379Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8d/a8828e3eaf5c0b4ab116924df82f24ce3416fa38d0674d8f708ddc6c8aac/numpy-2.4.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1889b3a3f47a7b5bee16bc25a2145bd7cb91897f815ce3499db64c7458b6d91d", size = 14456053, upload-time = "2025-12-20T16:17:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/17d97609d87d4520aa5ae2dcfb32305654550ac6a35effb946d303e594ce/numpy-2.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85eef4cb5625c47ee6425c58a3502555e10f45ee973da878ac8248ad58c136f3", size = 16401702, upload-time = "2025-12-20T16:17:04.235Z" }, + { url = "https://files.pythonhosted.org/packages/18/32/0f13c1b2d22bea1118356b8b963195446f3af124ed7a5adfa8fdecb1b6ca/numpy-2.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6dc8b7e2f4eb184b37655195f421836cfae6f58197b67e3ffc501f1333d993fa", size = 16242493, upload-time = "2025-12-20T16:17:06.856Z" }, + { url = "https://files.pythonhosted.org/packages/ae/23/48f21e3d309fbc137c068a1475358cbd3a901b3987dcfc97a029ab3068e2/numpy-2.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:44aba2f0cafd287871a495fb3163408b0bd25bbce135c6f621534a07f4f7875c", size = 18324222, upload-time = "2025-12-20T16:17:09.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/52/41f3d71296a3dcaa4f456aaa3c6fc8e745b43d0552b6bde56571bb4b4a0f/numpy-2.4.0-cp313-cp313t-win32.whl", hash = "sha256:20c115517513831860c573996e395707aa9fb691eb179200125c250e895fcd93", size = 6076216, upload-time = "2025-12-20T16:17:11.437Z" }, + { url = "https://files.pythonhosted.org/packages/35/ff/46fbfe60ab0710d2a2b16995f708750307d30eccbb4c38371ea9e986866e/numpy-2.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b48e35f4ab6f6a7597c46e301126ceba4c44cd3280e3750f85db48b082624fa4", size = 12444263, upload-time = "2025-12-20T16:17:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e3/9189ab319c01d2ed556c932ccf55064c5d75bb5850d1df7a482ce0badead/numpy-2.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4d1cfce39e511069b11e67cd0bd78ceff31443b7c9e5c04db73c7a19f572967c", size = 10378265, upload-time = "2025-12-20T16:17:15.211Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.3.20" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145, upload-time = "2025-08-04T20:25:19.995Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "optuna" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "colorlog" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/81/08f90f194eed78178064a9383432eca95611e2c5331e7b01e2418ce4b15a/optuna-4.6.0.tar.gz", hash = "sha256:89e38c2447c7f793a726617b8043f01e31f0bad54855040db17eb3b49404a369", size = 477444, upload-time = "2025-11-10T05:14:30.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/de/3d8455b08cb6312f8cc46aacdf16c71d4d881a1db4a4140fc5ef31108422/optuna-4.6.0-py3-none-any.whl", hash = "sha256:4c3a9facdef2b2dd7e3e2a8ae3697effa70fae4056fcf3425cfc6f5a40feb069", size = 404708, upload-time = "2025-11-10T05:14:28.6Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pathlib" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298, upload-time = "2014-09-03T15:41:57.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363, upload-time = "2022-05-04T13:37:20.585Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "patsy" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pip" +version = "25.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014, upload-time = "2025-10-25T00:55:41.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, +] + +[[package]] +name = "pip-system-certs" +version = "5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/6a/563b05a4f6c9ddc205c98bb413e74221368efb98b8fb9cca96b578b8930c/pip_system_certs-5.3.tar.gz", hash = "sha256:19c8bf9957bcce7d69c4dbc2d0b2ef13de1984d53f50a59012e6dbbad0af67c6", size = 6395, upload-time = "2025-10-16T06:14:55.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/57/752b63c609affae8f26ae0f1d1103d6ea7e707ad45943f62f7422936071d/pip_system_certs-5.3-py3-none-any.whl", hash = "sha256:3fbb5de62e374a99b688b1ad06e64ee5c4aeb633ef23e3a677d32e3e84fd863c", size = 6896, upload-time = "2025-10-16T06:14:54.072Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916, upload-time = "2024-05-15T03:18:23.372Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146, upload-time = "2024-05-15T03:18:21.209Z" }, +] + +[[package]] +name = "plotly" +version = "5.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4f/428f6d959818d7425a94c190a6b26fbc58035cbef40bf249be0b62a9aedd/plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae", size = 9479398, upload-time = "2024-09-12T15:36:31.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ae/580600f441f6fc05218bd6c9d5794f4aef072a7d9093b291f1c50a9db8bc/plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089", size = 19054220, upload-time = "2024-09-12T15:36:24.08Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "polars" +version = "1.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "polars-runtime-32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/dc/56f2a90c79a2cb13f9e956eab6385effe54216ae7a2068b3a6406bae4345/polars-1.36.1.tar.gz", hash = "sha256:12c7616a2305559144711ab73eaa18814f7aa898c522e7645014b68f1432d54c", size = 711993, upload-time = "2025-12-10T01:14:53.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/c6/36a1b874036b49893ecae0ac44a2f63d1a76e6212631a5b2f50a86e0e8af/polars-1.36.1-py3-none-any.whl", hash = "sha256:853c1bbb237add6a5f6d133c15094a9b727d66dd6a4eb91dbb07cdb056b2b8ef", size = 802429, upload-time = "2025-12-10T01:13:53.838Z" }, +] + +[package.optional-dependencies] +pyarrow = [ + { name = "pyarrow" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.36.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/df/597c0ef5eb8d761a16d72327846599b57c5d40d7f9e74306fc154aba8c37/polars_runtime_32-1.36.1.tar.gz", hash = "sha256:201c2cfd80ceb5d5cd7b63085b5fd08d6ae6554f922bcb941035e39638528a09", size = 2788751, upload-time = "2025-12-10T01:14:54.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/ea/871129a2d296966c0925b078a9a93c6c5e7facb1c5eebfcd3d5811aeddc1/polars_runtime_32-1.36.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:327b621ca82594f277751f7e23d4b939ebd1be18d54b4cdf7a2f8406cecc18b2", size = 43494311, upload-time = "2025-12-10T01:13:56.096Z" }, + { url = "https://files.pythonhosted.org/packages/d8/76/0038210ad1e526ce5bb2933b13760d6b986b3045eccc1338e661bd656f77/polars_runtime_32-1.36.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ab0d1f23084afee2b97de8c37aa3e02ec3569749ae39571bd89e7a8b11ae9e83", size = 39300602, upload-time = "2025-12-10T01:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/54/1e/2707bee75a780a953a77a2c59829ee90ef55708f02fc4add761c579bf76e/polars_runtime_32-1.36.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:899b9ad2e47ceb31eb157f27a09dbc2047efbf4969a923a6b1ba7f0412c3e64c", size = 44511780, upload-time = "2025-12-10T01:14:02.285Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/3fede95feee441be64b4bcb32444679a8fbb7a453a10251583053f6efe52/polars_runtime_32-1.36.1-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d9d077bb9df711bc635a86540df48242bb91975b353e53ef261c6fae6cb0948f", size = 40688448, upload-time = "2025-12-10T01:14:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/05/0f/e629713a72999939b7b4bfdbf030a32794db588b04fdf3dc977dd8ea6c53/polars_runtime_32-1.36.1-cp39-abi3-win_amd64.whl", hash = "sha256:cc17101f28c9a169ff8b5b8d4977a3683cd403621841623825525f440b564cf0", size = 44464898, upload-time = "2025-12-10T01:14:08.296Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d8/a12e6aa14f63784cead437083319ec7cece0d5bb9a5bfe7678cc6578b52a/polars_runtime_32-1.36.1-cp39-abi3-win_arm64.whl", hash = "sha256:809e73857be71250141225ddd5d2b30c97e6340aeaa0d445f930e01bef6888dc", size = 39798896, upload-time = "2025-12-10T01:14:11.568Z" }, +] + +[[package]] +name = "policyengine-core" +version = "3.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dpath" }, + { name = "h5py" }, + { name = "huggingface-hub" }, + { name = "ipython" }, + { name = "microdf-python" }, + { name = "numexpr" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "psutil" }, + { name = "pytest" }, + { name = "pyvis" }, + { name = "requests" }, + { name = "sortedcontainers" }, + { name = "standard-imghdr" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ff/8a239cca030ad865f815f812725b7d0f2fb9839175c0f0d5e2b392247d6f/policyengine_core-3.23.1.tar.gz", hash = "sha256:a2de3b4398142cb70bdb08b8a64dbb1766f2cbf08cb21c37a83e8650c7d8e075", size = 162809, upload-time = "2025-12-14T23:37:36.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/23/8367becc01e99f1450302eee90a735a4bda10d77f583cb51b98fcad03d51/policyengine_core-3.23.1-py3-none-any.whl", hash = "sha256:49265216e0a90ae727d6f90a70ffc350949aa283e73d60cbed98d5ccbdefbc5e", size = 224715, upload-time = "2025-12-14T23:37:35.151Z" }, +] + +[[package]] +name = "policyengine-us" +version = "1.484.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microdf-python" }, + { name = "policyengine-core" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/9f/260253fe7ded419131a4737a3ed98e1d160f730885d784b4392a22f59f57/policyengine_us-1.484.0.tar.gz", hash = "sha256:8afdb5f8917b636654f5d6283aaa664b17abba0baec7b75e503a76bd817b37c5", size = 8363259, upload-time = "2025-12-29T16:43:27.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/96/23cd3a8e768f249677949a43b715336448d7ed814be6ae4c8ad1edf37028/policyengine_us-1.484.0-py3-none-any.whl", hash = "sha256:f6897b562722daf4f9f8ecdeb2044775d39bf9eb80472ff6b86116d8d229d7fa", size = 6899347, upload-time = "2025-12-29T16:43:24.812Z" }, +] + +[[package]] +name = "policyengine-us-data" +version = "1.50.0" +source = { editable = "." } +dependencies = [ + { name = "google-auth" }, + { name = "google-cloud-storage" }, + { name = "microdf-python" }, + { name = "microimpute" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "pip-system-certs" }, + { name = "policyengine-core" }, + { name = "policyengine-us" }, + { name = "requests" }, + { name = "scipy" }, + { name = "setuptools" }, + { name = "spm-calculator" }, + { name = "sqlalchemy" }, + { name = "sqlmodel" }, + { name = "statsmodels" }, + { name = "tables" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "us" }, + { name = "xlrd" }, +] + +[package.optional-dependencies] +calibration = [ + { name = "samplics" }, +] +dev = [ + { name = "black" }, + { name = "build" }, + { name = "furo" }, + { name = "itables" }, + { name = "jupyter-book" }, + { name = "mystmd" }, + { name = "pytest" }, + { name = "quantile-forest" }, + { name = "tabulate" }, + { name = "tomli" }, + { name = "yaml-changelog" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'" }, + { name = "build", marker = "extra == 'dev'" }, + { name = "furo", marker = "extra == 'dev'" }, + { name = "google-auth", specifier = ">=2.0.0" }, + { name = "google-cloud-storage", specifier = ">=2.0.0" }, + { name = "itables", marker = "extra == 'dev'" }, + { name = "jupyter-book", marker = "extra == 'dev'" }, + { name = "microdf-python", specifier = ">=1.0.0" }, + { name = "microimpute", specifier = ">=1.1.4" }, + { name = "mystmd", marker = "extra == 'dev'", specifier = ">=1.7.0" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "pandas", specifier = ">=2.3.1" }, + { name = "pip-system-certs", specifier = ">=3.0" }, + { name = "policyengine-core", specifier = ">=3.19.0" }, + { name = "policyengine-us", specifier = ">=1.353.0" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "quantile-forest", marker = "extra == 'dev'" }, + { name = "requests", specifier = ">=2.25.0" }, + { name = "samplics", marker = "extra == 'calibration'" }, + { name = "scipy", specifier = ">=1.15.3" }, + { name = "setuptools", specifier = ">=60" }, + { name = "spm-calculator", specifier = ">=0.1.0" }, + { name = "sqlalchemy", specifier = ">=2.0.41" }, + { name = "sqlmodel", specifier = ">=0.0.24" }, + { name = "statsmodels", specifier = ">=0.14.5" }, + { name = "tables", specifier = ">=3.10.2" }, + { name = "tabulate", marker = "extra == 'dev'" }, + { name = "tomli", marker = "extra == 'dev'" }, + { name = "torch", specifier = ">=2.7.1" }, + { name = "tqdm", specifier = ">=4.60.0" }, + { name = "us", specifier = ">=2.0.0" }, + { name = "xlrd", specifier = ">=2.0.2" }, + { name = "yaml-changelog", marker = "extra == 'dev'", specifier = ">=0.1.7" }, +] +provides-extras = ["calibration", "dev"] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158, upload-time = "2025-12-16T13:46:25.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205, upload-time = "2025-12-16T13:46:24.76Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, +] + +[[package]] +name = "psutil" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502, upload-time = "2024-12-19T18:21:20.568Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511, upload-time = "2024-12-19T18:21:45.163Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985, upload-time = "2024-12-19T18:21:49.254Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488, upload-time = "2024-12-19T18:21:51.638Z" }, + { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477, upload-time = "2024-12-19T18:21:55.306Z" }, + { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017, upload-time = "2024-12-19T18:21:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602, upload-time = "2024-12-19T18:22:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444, upload-time = "2024-12-19T18:22:11.335Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + +[[package]] +name = "pyarrow" +version = "22.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" }, + { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a", size = 34204629, upload-time = "2025-10-24T10:06:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901", size = 35985783, upload-time = "2025-10-24T10:06:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/782344c2ce58afbea010150df07e3a2f5fdad299cd631697ae7bd3bac6e3/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691", size = 45020999, upload-time = "2025-10-24T10:06:35.387Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a", size = 47724601, upload-time = "2025-10-24T10:06:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/69/4d/76e567a4fc2e190ee6072967cb4672b7d9249ac59ae65af2d7e3047afa3b/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6", size = 48001050, upload-time = "2025-10-24T10:06:52.284Z" }, + { url = "https://files.pythonhosted.org/packages/01/5e/5653f0535d2a1aef8223cee9d92944cb6bccfee5cf1cd3f462d7cb022790/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941", size = 50307877, upload-time = "2025-10-24T10:07:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145", size = 27977099, upload-time = "2025-10-24T10:08:07.259Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/db56870c997805bf2b0f6eeeb2d68458bf4654652dccdcf1bf7a42d80903/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1", size = 34336685, upload-time = "2025-10-24T10:07:11.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/98/0727947f199aba8a120f47dfc229eeb05df15bcd7a6f1b669e9f882afc58/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f", size = 36032158, upload-time = "2025-10-24T10:07:18.626Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/9babdef9c01720a0785945c7cf550e4acd0ebcd7bdd2e6f0aa7981fa85e2/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d", size = 44892060, upload-time = "2025-10-24T10:07:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ca/2f8804edd6279f78a37062d813de3f16f29183874447ef6d1aadbb4efa0f/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f", size = 47504395, upload-time = "2025-10-24T10:07:34.09Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", size = 48066216, upload-time = "2025-10-24T10:07:43.528Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", size = 50288552, upload-time = "2025-10-24T10:07:53.519Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", size = 28262504, upload-time = "2025-10-24T10:08:00.932Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + +[[package]] +name = "pytokens" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyvis" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython" }, + { name = "jinja2" }, + { name = "jsonpickle" }, + { name = "networkx" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/4b/e37e4e5d5ee1179694917b445768bdbfb084f5a59ecd38089d3413d4c70f/pyvis-0.3.2-py3-none-any.whl", hash = "sha256:5720c4ca8161dc5d9ab352015723abb7a8bb8fb443edeb07f7a322db34a97555", size = 756038, upload-time = "2023-02-24T20:29:46.758Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391, upload-time = "2025-10-03T21:19:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, +] + +[[package]] +name = "quantile-forest" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/6e/3f1493d4abcce71fdc82ed575475d3e02da7b03375129e84be2622e1532f/quantile_forest-1.4.1.tar.gz", hash = "sha256:713a23c69562b7551ba4a05c22ce9d0e90db6a73d043e760b29c331cb19dc552", size = 486249, upload-time = "2025-09-10T12:48:04.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/53/63c400659404b45221405f7dbdb42fb0cea4b9cae0877a567d56d760a995/quantile_forest-1.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7d4eae276928f07c13e4784842768569e92c50e93f66c1feadf85c4967b3be4", size = 959038, upload-time = "2025-09-10T12:47:45.193Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d7/694d428f94b5aec95bd9bb3805b119c1845bb63e215deeeab64e60812037/quantile_forest-1.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0526c117be0df98e79e1ce378968f1e1faa9ca23e08da449baa0651a52a81d1", size = 720471, upload-time = "2025-09-10T12:47:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/747bf715bfba7570f88c7c601ef3f3350eceb4ce4bf72a1d36fb9845fdd2/quantile_forest-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67fc17c82ea85f575617f7a093f3ad8ef0dc5a159f886a9948224b98483ad8c", size = 710769, upload-time = "2025-09-10T12:47:47.88Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/86bbce5503c007cfeeb74068edf608c4216e570ad13c9500513f5473740c/quantile_forest-1.4.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d402c4af3f72d21c3ca3e9dda25a68207d29ae4d34b8126bcf19fc3680ce23e0", size = 2406284, upload-time = "2025-09-10T12:47:49.42Z" }, + { url = "https://files.pythonhosted.org/packages/8b/93/1ae45144ab80bdd8cf8e7bf983137440b1c3430516a7db340caee9b6d77d/quantile_forest-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:b1513b039f7ea5b9467201807b41594d25ecaf088868221e2f1ddea4edeb13b8", size = 685743, upload-time = "2025-09-10T12:47:50.525Z" }, + { url = "https://files.pythonhosted.org/packages/33/61/f8ff4e348dc2d265ea97287f921b92bca265229c48be64b94756ecff4078/quantile_forest-1.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:37c2da2ab54aceacdf5292065147f40a073b13cc3844262f0f3cbd5b8a8d928e", size = 955098, upload-time = "2025-09-10T12:47:52.137Z" }, + { url = "https://files.pythonhosted.org/packages/4f/95/75f3eea1c7cc3786c1ffdf4685e79c4979a4ae6ccedfed80362c9162f0d4/quantile_forest-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f0436ac7622442c2995cf121e0960332e769791f3f3c7ea62363e8480803bb3", size = 718470, upload-time = "2025-09-10T12:47:53.566Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f1/0f26386bf164ede156099d18e3e4493dd21dc48e329e1be68232e5cf8b52/quantile_forest-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a594bd3552507beffa6ca6002143601be5defd5cc7329154f41317110f895f7a", size = 709245, upload-time = "2025-09-10T12:47:54.54Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cd/6501c8c200f34a87e1e94d7ea4f1a9dc842154fbfaa0fe65f072817fbc41/quantile_forest-1.4.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:697c48faf52a04e7e47f97187650d16cecc9c971fe2f83d56854b4a454289f60", size = 2403543, upload-time = "2025-09-10T12:47:55.956Z" }, + { url = "https://files.pythonhosted.org/packages/f2/be/f77c6705e974b23353c43da1cd93e11fe0afc7e859c2d14f748d25cc0376/quantile_forest-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe33f6a8b63b3617568cc1254e1802a70ce3ac23897790f3be10f8db5257fe83", size = 685417, upload-time = "2025-09-10T12:47:57.346Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "samplics" +version = "0.4.55" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "polars", extra = ["pyarrow"] }, + { name = "statsmodels" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/b5/e5eaafb91852ca68202f5d9e8a3fc4a0b0aa28d2260f4431ece47581e8ee/samplics-0.4.55.tar.gz", hash = "sha256:19f829b892d48ffa144a9683e2df5908ddb52b1322d1850aeb87d1034938cac7", size = 3345671, upload-time = "2025-08-22T00:03:25.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/57/bd011568272351a534970a1e590ec7c06c7bf7080dee1fb79a3d9472a594/samplics-0.4.55-py3-none-any.whl", hash = "sha256:053e8d916cce6f0ba7de7b6b9633a808177752dbe059d0af13c25e067c75ec25", size = 246176, upload-time = "2025-08-22T00:03:24.175Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, + { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" }, + { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" }, + { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" }, + { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" }, + { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" }, + { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "spm-calculator" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "census" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "requests" }, + { name = "us" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/d0/f96e42ab45515e491b654e46d59318002a18e5268d3264b26566a71f8c43/spm_calculator-0.1.0.tar.gz", hash = "sha256:539ee27cccec20fd80e25627b8f1d1f7a65663187f79bdd336a873110cfae240", size = 24992, upload-time = "2025-12-19T03:20:43.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/06/596f4a1012984f5011ee8e6e522c0d83ae27eea74cecee42f75892aa6fb5/spm_calculator-0.1.0-py3-none-any.whl", hash = "sha256:ef713361aac5bf764d7892be724eb69a99ec61631a75d8904a361b73fbc5b0f7", size = 19889, upload-time = "2025-12-19T03:20:42.27Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/c7/1900b56ce19bff1c26f39a4ce427faec7716c81ac792bfac8b6a9f3dca93/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f", size = 3333760, upload-time = "2025-12-09T22:11:02.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/93/3be94d96bb442d0d9a60e55a6bb6e0958dd3457751c6f8502e56ef95fed0/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d", size = 3348268, upload-time = "2025-12-09T22:13:49.054Z" }, + { url = "https://files.pythonhosted.org/packages/48/4b/f88ded696e61513595e4a9778f9d3f2bf7332cce4eb0c7cedaabddd6687b/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4", size = 3278144, upload-time = "2025-12-09T22:11:04.14Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6a/310ecb5657221f3e1bd5288ed83aa554923fb5da48d760a9f7622afeb065/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6", size = 3313907, upload-time = "2025-12-09T22:13:50.598Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/69c0b4051079addd57c84a5bfb34920d87456dd4c90cf7ee0df6efafc8ff/sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953", size = 2112182, upload-time = "2025-12-09T21:39:30.824Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4e/510db49dd89fc3a6e994bee51848c94c48c4a00dc905e8d0133c251f41a7/sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1", size = 2139200, upload-time = "2025-12-09T21:39:32.321Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" }, + { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" }, + { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" }, + { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.31" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload-time = "2025-12-28T12:35:01.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload-time = "2025-12-28T12:35:00.108Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "standard-imghdr" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/8d/ab2620fbe2e348483c9cb776c3b7b3cc407899291a041d7fa026469b7cd1/standard_imghdr-3.13.0.tar.gz", hash = "sha256:8d9c68058d882f6fc3542a8d39ef9ff94d2187dc90bd0c851e0902776b7b7a42", size = 5511, upload-time = "2024-10-30T16:01:36.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/cb/e1da7e340586a078404c7e4328bfefc930867ace8a9a55916fd220cf9547/standard_imghdr-3.13.0-py3-none-any.whl", hash = "sha256:30a1bff5465605bb496f842a6ac3cc1f2131bf3025b0da28d4877d6d4b7cc8e9", size = 4639, upload-time = "2024-10-30T16:01:13.829Z" }, +] + +[[package]] +name = "statsmodels" +version = "0.14.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932, upload-time = "2025-12-05T19:28:55.446Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345, upload-time = "2025-12-05T19:29:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649, upload-time = "2025-12-05T23:10:12.775Z" }, + { url = "https://files.pythonhosted.org/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446, upload-time = "2025-12-05T23:10:28.503Z" }, + { url = "https://files.pythonhosted.org/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705, upload-time = "2025-12-05T23:10:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" }, + { url = "https://files.pythonhosted.org/packages/81/59/a5aad5b0cc266f5be013db8cde563ac5d2a025e7efc0c328d83b50c72992/statsmodels-0.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47ee7af083623d2091954fa71c7549b8443168f41b7c5dce66510274c50fd73e", size = 10072009, upload-time = "2025-12-05T23:11:14.021Z" }, + { url = "https://files.pythonhosted.org/packages/53/dd/d8cfa7922fc6dc3c56fa6c59b348ea7de829a94cd73208c6f8202dd33f17/statsmodels-0.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa60d82e29fcd0a736e86feb63a11d2380322d77a9369a54be8b0965a3985f71", size = 9980018, upload-time = "2025-12-05T23:11:30.907Z" }, + { url = "https://files.pythonhosted.org/packages/ee/77/0ec96803eba444efd75dba32f2ef88765ae3e8f567d276805391ec2c98c6/statsmodels-0.14.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ee7d595f5939cc20bf946faedcb5137d975f03ae080f300ebb4398f16a5bd4", size = 10060269, upload-time = "2025-12-05T23:11:46.338Z" }, + { url = "https://files.pythonhosted.org/packages/10/b9/fd41f1f6af13a1a1212a06bb377b17762feaa6d656947bf666f76300fc05/statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:730f3297b26749b216a06e4327fe0be59b8d05f7d594fb6caff4287b69654589", size = 10324155, upload-time = "2025-12-05T23:12:01.805Z" }, + { url = "https://files.pythonhosted.org/packages/ee/0f/a6900e220abd2c69cd0a07e3ad26c71984be6061415a60e0f17b152ecf08/statsmodels-0.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f1c08befa85e93acc992b72a390ddb7bd876190f1360e61d10cf43833463bc9c", size = 10349765, upload-time = "2025-12-05T23:12:18.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/08/b79f0c614f38e566eebbdcff90c0bcacf3c6ba7a5bbb12183c09c29ca400/statsmodels-0.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:8021271a79f35b842c02a1794465a651a9d06ec2080f76ebc3b7adce77d08233", size = 9540043, upload-time = "2025-12-05T23:12:33.887Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tables" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blosc2" }, + { name = "numexpr" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "py-cpuinfo" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/50/23ead25f60bb1babe7f2f061d8a2f8c2f6804c1a20b3058677beb9085b56/tables-3.10.2.tar.gz", hash = "sha256:2544812a7186fadba831d6dd34eb49ccd788d6a83f4e4c2b431b835b6796c910", size = 4779722, upload-time = "2025-01-04T20:44:13.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/c4/1efbcc699db863d88874f3d111e5bb6dd2e0fbaca38f91c992e696324730/tables-3.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6ba58205d1f6a4e0e2212bc221e76cf104f22190f90c3f1683f3c1ab138f28f", size = 6734990, upload-time = "2025-01-04T20:43:20.794Z" }, + { url = "https://files.pythonhosted.org/packages/4a/db/4c7facfc805ab764f2ee256011d20f96791d2426afa3389ca7ff2a8a4ea8/tables-3.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdb5c040aa43e5e96259d6f6bb9df5b66fef2b071a6eb035c21bf6508e865d40", size = 5483377, upload-time = "2025-01-04T20:43:25.923Z" }, + { url = "https://files.pythonhosted.org/packages/93/0a/53815b516a2465b329e5dc2079c99a8b6b1a23f6b9ce5da8a7ebc7892bf4/tables-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e694123fa886d9be57f55fc7e1dcacac49f0b4ed4a931c795bd8f82f7111b5a8", size = 7081356, upload-time = "2025-01-04T20:43:31.066Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e1/3f4adfc83eb7390abb964682a7d1df0dbe451dd2cee99750b1c7ca8e2c9d/tables-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c12d0d04de89297763923ebeaddfd7e0b51f29041895db284fd4913e7448b7", size = 7483570, upload-time = "2025-01-04T20:43:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/9a/d4/0b9ba57a5a8d2d05d1108055a8d70a4b066db4ebed61921de34043a31bdb/tables-3.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:a406d5dbbcb6604bd1ca129af337e0790d4e02d29d06159ddb9f74e38d756d32", size = 6388443, upload-time = "2025-01-04T20:43:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/ab/02/8c7aeaa6c8aac8e0298d40dc5fc55477fddc30cb31e4dc7e5e473be4b464/tables-3.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7b8bc07c715bad3d447ed8f834388ef2e10265e2c4af6b1297fc61adb645948f", size = 6725764, upload-time = "2025-01-04T20:43:48.171Z" }, + { url = "https://files.pythonhosted.org/packages/91/f4/8683395d294b9e4576fd7d888aa6cf5583c013c2c0a2e47f862c2842407f/tables-3.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:28677ed8e1a371471495599078f48da0850f82457d6c852ca77959c974371140", size = 5442663, upload-time = "2025-01-04T20:43:53.722Z" }, + { url = "https://files.pythonhosted.org/packages/72/9b/ea43159eed8f81bfa1ead8fa8201a3c352e84c7220e046bb548736833951/tables-3.10.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaaea478dcf27dd54679ef2643c26d3b8b15676ad81e4d80a88fd1682d23deb1", size = 7078747, upload-time = "2025-01-04T20:43:59.596Z" }, + { url = "https://files.pythonhosted.org/packages/04/95/b3e88edc674e35d9011b168df0d7a9b1c3ab98733fa26e740ac7964edc2f/tables-3.10.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5e67a9f901842f9a4b1f3d2307f4bdd94047514fe0d0c558ed19c11f53c402a", size = 7479985, upload-time = "2025-01-04T20:44:04.13Z" }, + { url = "https://files.pythonhosted.org/packages/63/ca/eaa029a43d269bdda6985931d6cfd479e876cd8cf7c887d818bef05ef03b/tables-3.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:5637fdcded5ba5426aa24e0e42d6f990926a4da7f193830df131dfcb7e842900", size = 6385562, upload-time = "2025-01-04T20:44:08.196Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "torch" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/27/07c645c7673e73e53ded71705045d6cb5bae94c4b021b03aa8d03eee90ab/torch-2.9.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:da5f6f4d7f4940a173e5572791af238cb0b9e21b1aab592bd8b26da4c99f1cd6", size = 104126592, upload-time = "2025-11-12T15:20:41.62Z" }, + { url = "https://files.pythonhosted.org/packages/19/17/e377a460603132b00760511299fceba4102bd95db1a0ee788da21298ccff/torch-2.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:27331cd902fb4322252657f3902adf1c4f6acad9dcad81d8df3ae14c7c4f07c4", size = 899742281, upload-time = "2025-11-12T15:22:17.602Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1a/64f5769025db846a82567fa5b7d21dba4558a7234ee631712ee4771c436c/torch-2.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:81a285002d7b8cfd3fdf1b98aa8df138d41f1a8334fd9ea37511517cedf43083", size = 110940568, upload-time = "2025-11-12T15:21:18.689Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ab/07739fd776618e5882661d04c43f5b5586323e2f6a2d7d84aac20d8f20bd/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:c0d25d1d8e531b8343bea0ed811d5d528958f1dcbd37e7245bc686273177ad7e", size = 74479191, upload-time = "2025-11-12T15:21:25.816Z" }, + { url = "https://files.pythonhosted.org/packages/20/60/8fc5e828d050bddfab469b3fe78e5ab9a7e53dda9c3bdc6a43d17ce99e63/torch-2.9.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c29455d2b910b98738131990394da3e50eea8291dfeb4b12de71ecf1fdeb21cb", size = 104135743, upload-time = "2025-11-12T15:21:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/6d3f80e6918213babddb2a37b46dbb14c15b14c5f473e347869a51f40e1f/torch-2.9.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:524de44cd13931208ba2c4bde9ec7741fd4ae6bfd06409a604fc32f6520c2bc9", size = 899749493, upload-time = "2025-11-12T15:24:36.356Z" }, + { url = "https://files.pythonhosted.org/packages/a6/47/c7843d69d6de8938c1cbb1eba426b1d48ddf375f101473d3e31a5fc52b74/torch-2.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:545844cc16b3f91e08ce3b40e9c2d77012dd33a48d505aed34b7740ed627a1b2", size = 110944162, upload-time = "2025-11-12T15:21:53.151Z" }, + { url = "https://files.pythonhosted.org/packages/28/0e/2a37247957e72c12151b33a01e4df651d9d155dd74d8cfcbfad15a79b44a/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5be4bf7496f1e3ffb1dd44b672adb1ac3f081f204c5ca81eba6442f5f634df8e", size = 74830751, upload-time = "2025-11-12T15:21:43.792Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f7/7a18745edcd7b9ca2381aa03353647bca8aace91683c4975f19ac233809d/torch-2.9.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:30a3e170a84894f3652434b56d59a64a2c11366b0ed5776fab33c2439396bf9a", size = 104142929, upload-time = "2025-11-12T15:21:48.319Z" }, + { url = "https://files.pythonhosted.org/packages/f4/dd/f1c0d879f2863ef209e18823a988dc7a1bf40470750e3ebe927efdb9407f/torch-2.9.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8301a7b431e51764629208d0edaa4f9e4c33e6df0f2f90b90e261d623df6a4e2", size = 899748978, upload-time = "2025-11-12T15:23:04.568Z" }, + { url = "https://files.pythonhosted.org/packages/1f/9f/6986b83a53b4d043e36f3f898b798ab51f7f20fdf1a9b01a2720f445043d/torch-2.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2e1c42c0ae92bf803a4b2409fdfed85e30f9027a66887f5e7dcdbc014c7531db", size = 111176995, upload-time = "2025-11-12T15:22:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/40/60/71c698b466dd01e65d0e9514b5405faae200c52a76901baf6906856f17e4/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:2c14b3da5df416cf9cb5efab83aa3056f5b8cd8620b8fde81b4987ecab730587", size = 74480347, upload-time = "2025-11-12T15:21:57.648Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "triton" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207, upload-time = "2025-11-11T17:41:00.253Z" }, + { url = "https://files.pythonhosted.org/packages/27/46/8c3bbb5b0a19313f50edcaa363b599e5a1a5ac9683ead82b9b80fe497c8d/triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4346b6ebbd4fad18773f5ba839114f4826037c9f2f34e0148894cd5dd3dba", size = 170470410, upload-time = "2025-11-11T17:41:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/37/92/e97fcc6b2c27cdb87ce5ee063d77f8f26f19f06916aa680464c8104ef0f6/triton-3.5.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d2c70127fca6a23e247f9348b8adde979d2e7a20391bfbabaac6aebc7e6a8", size = 170579924, upload-time = "2025-11-11T17:41:12.455Z" }, +] + +[[package]] +name = "typer-slim" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/3b/2f60ce16f578b1db5b8816d37d6a4d9786b33b76407fc8c13b0b86312c31/typer_slim-0.21.0.tar.gz", hash = "sha256:f2dbd150cfa0fead2242e21fa9f654dfc64773763ddf07c6be9a49ad34f79557", size = 106841, upload-time = "2025-12-25T09:54:55.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/84/e97abf10e4a699194ff07fd586ec7f4cf867d9d04bead559a65f9e7aff84/typer_slim-0.21.0-py3-none-any.whl", hash = "sha256:92aee2188ac6fc2b2924bd75bb61a340b78bd8cd51fd9735533ce5a856812c8e", size = 47174, upload-time = "2025-12-25T09:54:54.609Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, +] + +[[package]] +name = "us" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jellyfish" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/12/06f87be706ccc5794569d14f903c2f755aa98e1a9d53e4e7e17d9986e9d1/us-3.2.0.tar.gz", hash = "sha256:cb223e85393dcc5171ead0dd212badc47f9667b23700fea3e7ea5f310d545338", size = 16046, upload-time = "2024-07-22T01:09:42.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/a8/1791660a87f03d10a3bce00401a66035999c91f5a9a6987569b84df5719d/us-3.2.0-py3-none-any.whl", hash = "sha256:571714ad6d473c72bbd2058a53404cdf4ecc0129e4f19adfcbeb4e2d7e3dc3e7", size = 13775, upload-time = "2024-07-22T01:09:41.432Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "webcolors" +version = "25.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] + +[[package]] +name = "xlrd" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, +] + +[[package]] +name = "yaml-changelog" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argparse" }, + { name = "datetime" }, + { name = "pathlib" }, + { name = "pyyaml" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/49/1004cdb8f58f49e136927b6b82554720f8f290269c4a2fe00ddf84f95dc5/yaml-changelog-0.3.0.tar.gz", hash = "sha256:d3a0f6921f8702200b16ecc3dbe6de839b7838544e68af6437ae2ecc67d83819", size = 3937, upload-time = "2022-10-18T17:50:21.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/e5/b28588e1e05392c7d4bcf300673ba563323b02b217f78926f6347c461407/yaml_changelog-0.3.0-py3-none-any.whl", hash = "sha256:d9b5f325efb1c9fb8461c5fec3d94c7bc5259c8f8e37ba0a790b01a07e9487f3", size = 16993, upload-time = "2022-10-18T17:50:20.173Z" }, +] + +[[package]] +name = "zope-interface" +version = "8.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/c9/5ec8679a04d37c797d343f650c51ad67d178f0001c363e44b6ac5f97a9da/zope_interface-8.1.1.tar.gz", hash = "sha256:51b10e6e8e238d719636a401f44f1e366146912407b58453936b781a19be19ec", size = 254748, upload-time = "2025-11-15T08:32:52.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/3d/f5b8dd2512f33bfab4faba71f66f6873603d625212206dd36f12403ae4ca/zope_interface-8.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a16715808408db7252b8c1597ed9008bdad7bf378ed48eb9b0595fad4170e49d", size = 208660, upload-time = "2025-11-15T08:36:53.579Z" }, + { url = "https://files.pythonhosted.org/packages/e5/41/c331adea9b11e05ff9ac4eb7d3032b24c36a3654ae9f2bf4ef2997048211/zope_interface-8.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce6b58752acc3352c4aa0b55bbeae2a941d61537e6afdad2467a624219025aae", size = 208851, upload-time = "2025-11-15T08:36:54.854Z" }, + { url = "https://files.pythonhosted.org/packages/25/00/7a8019c3bb8b119c5f50f0a4869183a4b699ca004a7f87ce98382e6b364c/zope_interface-8.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:807778883d07177713136479de7fd566f9056a13aef63b686f0ab4807c6be259", size = 259292, upload-time = "2025-11-15T08:36:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fc/b70e963bf89345edffdd5d16b61e789fdc09365972b603e13785360fea6f/zope_interface-8.1.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50e5eb3b504a7d63dc25211b9298071d5b10a3eb754d6bf2f8ef06cb49f807ab", size = 264741, upload-time = "2025-11-15T08:36:57.675Z" }, + { url = "https://files.pythonhosted.org/packages/96/fe/7d0b5c0692b283901b34847f2b2f50d805bfff4b31de4021ac9dfb516d2a/zope_interface-8.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eee6f93b2512ec9466cf30c37548fd3ed7bc4436ab29cd5943d7a0b561f14f0f", size = 264281, upload-time = "2025-11-15T08:36:58.968Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2c/a7cebede1cf2757be158bcb151fe533fa951038cfc5007c7597f9f86804b/zope_interface-8.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:80edee6116d569883c58ff8efcecac3b737733d646802036dc337aa839a5f06b", size = 212327, upload-time = "2025-11-15T08:37:00.4Z" }, + { url = "https://files.pythonhosted.org/packages/85/81/3c3b5386ce4fba4612fd82ffb8a90d76bcfea33ca2b6399f21e94d38484f/zope_interface-8.1.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:84f9be6d959640de9da5d14ac1f6a89148b16da766e88db37ed17e936160b0b1", size = 209046, upload-time = "2025-11-15T08:37:01.473Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e3/32b7cb950c4c4326b3760a8e28e5d6f70ad15f852bfd8f9364b58634f74b/zope_interface-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:531fba91dcb97538f70cf4642a19d6574269460274e3f6004bba6fe684449c51", size = 209104, upload-time = "2025-11-15T08:37:02.887Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3d/c4c68e1752a5f5effa2c1f5eaa4fea4399433c9b058fb7000a34bfb1c447/zope_interface-8.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:fc65f5633d5a9583ee8d88d1f5de6b46cd42c62e47757cfe86be36fb7c8c4c9b", size = 259277, upload-time = "2025-11-15T08:37:04.389Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5b/cf4437b174af7591ee29bbad728f620cab5f47bd6e9c02f87d59f31a0dda/zope_interface-8.1.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efef80ddec4d7d99618ef71bc93b88859248075ca2e1ae1c78636654d3d55533", size = 264742, upload-time = "2025-11-15T08:37:05.613Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0e/0cf77356862852d3d3e62db9aadae5419a1a7d89bf963b219745283ab5ca/zope_interface-8.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49aad83525eca3b4747ef51117d302e891f0042b06f32aa1c7023c62642f962b", size = 264252, upload-time = "2025-11-15T08:37:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/2af54aa88b2fa172d12364116cc40d325fedbb1877c3bb031b0da6052855/zope_interface-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:71cf329a21f98cb2bd9077340a589e316ac8a415cac900575a32544b3dffcb98", size = 212330, upload-time = "2025-11-15T08:37:08.14Z" }, +] From 18abc8b4567c7e3755b7362f8efaa8b821017e96 Mon Sep 17 00:00:00 2001 From: "baogorek@gmail.com" Date: Mon, 29 Dec 2025 13:22:40 -0500 Subject: [PATCH 24/24] Use uv run for all Python commands in workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uv sync creates a virtual environment, but commands were running with system Python which still had stale cached packages. All make/python/pytest commands now use `uv run` to execute within the virtual environment where the locked dependencies are installed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/code_changes.yaml | 2 +- .github/workflows/local_area_publish.yaml | 2 +- .github/workflows/reusable_test.yaml | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index aadf5c9d..ebc7b6d2 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -39,7 +39,7 @@ jobs: - name: Install package run: uv sync --dev - name: Build package - run: python -m build + run: uv run python -m build - name: Publish a Python distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.github/workflows/local_area_publish.yaml b/.github/workflows/local_area_publish.yaml index 573475b2..08fcdd5b 100644 --- a/.github/workflows/local_area_publish.yaml +++ b/.github/workflows/local_area_publish.yaml @@ -53,7 +53,7 @@ jobs: gsutil cp gs://policyengine-us-data/checkpoints/completed_cities.txt . || true - name: Build and publish local area H5 files - run: make publish-local-area + run: uv run make publish-local-area - name: Upload checkpoint if: always() diff --git a/.github/workflows/reusable_test.yaml b/.github/workflows/reusable_test.yaml index 8e0025e4..ff147899 100644 --- a/.github/workflows/reusable_test.yaml +++ b/.github/workflows/reusable_test.yaml @@ -61,7 +61,7 @@ jobs: - name: Download data inputs if: inputs.full_suite - run: make download + run: uv run make download # Temporarily disabled - database target causing issues # - name: Create and load calibration targets database @@ -70,7 +70,7 @@ jobs: - name: Build datasets if: inputs.full_suite - run: make data + run: uv run make data env: TEST_LITE: ${{ !inputs.upload_data }} PYTHON_LOG_LEVEL: INFO @@ -78,14 +78,14 @@ jobs: - name: Build datasets for local area calibration if: inputs.full_suite run: | - LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/cps.py - LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/puf/puf.py - LOCAL_AREA_CALIBRATION=true python policyengine_us_data/datasets/cps/extended_cps.py - python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 + LOCAL_AREA_CALIBRATION=true uv run python policyengine_us_data/datasets/cps/cps.py + LOCAL_AREA_CALIBRATION=true uv run python policyengine_us_data/datasets/puf/puf.py + LOCAL_AREA_CALIBRATION=true uv run python policyengine_us_data/datasets/cps/extended_cps.py + uv run python policyengine_us_data/datasets/cps/local_area_calibration/create_stratified_cps.py 10500 - name: Run local area calibration tests if: inputs.full_suite - run: pytest policyengine_us_data/tests/test_local_area_calibration/ -v + run: uv run pytest policyengine_us_data/tests/test_local_area_calibration/ -v - name: Save calibration log if: inputs.full_suite @@ -95,14 +95,14 @@ jobs: path: calibration_log.csv - name: Run tests - run: pytest + run: uv run pytest - name: Upload data if: inputs.upload_data - run: make upload + run: uv run make upload - name: Test documentation builds - run: make documentation + run: uv run make documentation env: BASE_URL: ${{ inputs.deploy_docs && '/policyengine-us-data' || '' }}