diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 5a68eedcf..5fa6bd1a0 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -20,7 +20,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -28,7 +28,7 @@ jobs: - name: Check formatting run: black . -l 79 --check test: - name: Build and test + name: Test runs-on: ubuntu-latest env: HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} @@ -42,21 +42,19 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 - name: Install package - run: make install-uv + run: uv pip install -e ".[dev]" --system - name: Download data inputs run: make download env: HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} - name: Build datasets run: make data - env: - DATA_LITE: true - name: Save calibration log uses: actions/upload-artifact@v4 with: name: calibration_log.csv path: calibration_log.csv - name: Run tests - run: pytest + run: make test diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 642728ff4..8ee863396 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -18,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -44,13 +44,13 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 - 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: make install-uv + run: uv pip install -e ".[dev]" --system - name: Download data inputs run: make download env: @@ -63,7 +63,7 @@ jobs: name: calibration_log.csv path: calibration_log.csv - name: Run tests - run: pytest + run: make test - name: Upload data run: make upload env: diff --git a/.gitignore b/.gitignore index 9d52f6ca9..1e629c608 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ !tax_benefit.csv !demographics.csv !incomes_projection.csv -!policyengine_uk_data/datasets/frs/local_areas/**/*.csv +!policyengine_uk_data/datasets/local_areas/**/*.csv **/_build !policyengine_uk_data/storage/*.csv **/version.json diff --git a/Makefile b/Makefile index f0b9239cd..d9b901788 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,7 @@ test: pytest . install: - pip install policyengine-uk - pip install policyengine>=2.4 - pip install -e ".[dev]" --config-settings editable_mode=compat - -install-uv: - uv pip install --system "jupyter-book>=2.0.0a0" - uv pip install --system -e ".[dev]" --config-settings editable_mode=compat + uv pip install -e ".[dev]" --config-settings editable_mode=compat download: python policyengine_uk_data/storage/download_private_prerequisites.py @@ -21,27 +15,13 @@ download: upload: python policyengine_uk_data/storage/upload_completed_datasets.py -docker: - docker buildx build --platform linux/amd64 . -t policyengine-uk-data:latest - documentation: + pip install --pre "jupyter-book>=2" jb clean docs && jb build docs python docs/add_plotly_to_book.py docs data: - python policyengine_uk_data/datasets/frs/dwp_frs.py - python policyengine_uk_data/datasets/frs/frs.py - python policyengine_uk_data/datasets/frs/extended_frs.py - python policyengine_uk_data/datasets/frs/enhanced_frs.py - python policyengine_uk_data/datasets/frs/local_areas/constituencies/calibrate.py - python policyengine_uk_data/datasets/frs/local_areas/local_authorities/calibrate.py - python policyengine_uk_data/utils/create_multi_year_dataset.py - python policyengine_uk_data/storage/migrate_to_uk_single_year_datasets.py - -efrs: - python policyengine_uk_data/datasets/frs/enhanced_frs.py - python policyengine_uk_data/datasets/frs/local_areas/constituencies/calibrate.py - python policyengine_uk_data/datasets/frs/local_areas/local_authorities/calibrate.py + python policyengine_uk_data/datasets/create_datasets.py build: python -m build diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..72a8911d9 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: patch + changes: + changed: + - Moved to functional, simplified architecture. diff --git a/policyengine_uk_data/__init__.py b/policyengine_uk_data/__init__.py index 975d883b3..f2d74ff00 100644 --- a/policyengine_uk_data/__init__.py +++ b/policyengine_uk_data/__init__.py @@ -1 +1,8 @@ from .datasets import * +from .storage.download_private_prerequisites import ( + download_prerequisites, + check_prerequisites, +) + +# Check prerequisites on import and warn if missing +check_prerequisites() diff --git a/policyengine_uk_data/datasets/__init__.py b/policyengine_uk_data/datasets/__init__.py deleted file mode 100644 index 5166bfab5..000000000 --- a/policyengine_uk_data/datasets/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from .frs import * -from .spi import * - -DATASETS = [ - FRS_2020_21, - FRS_2021_22, - FRS_2022_23, - ReweightedFRS_2022_23, - EnhancedFRS_2022_23, - SPI_2020_21, -] diff --git a/policyengine_uk_data/datasets/frs/childcare/README.md b/policyengine_uk_data/datasets/childcare/README.md similarity index 100% rename from policyengine_uk_data/datasets/frs/childcare/README.md rename to policyengine_uk_data/datasets/childcare/README.md diff --git a/policyengine_uk_data/datasets/frs/childcare/takeup_rate.py b/policyengine_uk_data/datasets/childcare/takeup_rate.py similarity index 100% rename from policyengine_uk_data/datasets/frs/childcare/takeup_rate.py rename to policyengine_uk_data/datasets/childcare/takeup_rate.py diff --git a/policyengine_uk_data/datasets/create_datasets.py b/policyengine_uk_data/datasets/create_datasets.py new file mode 100644 index 000000000..4cffeecc1 --- /dev/null +++ b/policyengine_uk_data/datasets/create_datasets.py @@ -0,0 +1,158 @@ +from policyengine_uk_data.datasets.frs import create_frs +from policyengine_uk_data.storage import STORAGE_FOLDER +import logging +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk_data.utils.uprating import uprate_dataset +from policyengine_uk_data.utils.progress import ( + ProcessingProgress, + display_success_panel, + display_error_panel, +) + +logging.basicConfig(level=logging.INFO) + + +def main(): + """Create enhanced FRS dataset with rich progress tracking.""" + try: + progress_tracker = ProcessingProgress() + + # Define dataset creation steps + steps = [ + "Create base FRS dataset", + "Impute consumption", + "Impute wealth", + "Impute VAT", + "Impute public service usage", + "Impute income", + "Impute capital gains", + "Uprate to 2025", + "Calibrate dataset", + "Downrate to 2023", + "Save final dataset", + ] + + with progress_tracker.track_dataset_creation(steps) as ( + update_dataset, + nested_progress, + ): + + # Create base FRS dataset + update_dataset("Create base FRS dataset", "processing") + frs = create_frs( + raw_frs_folder=STORAGE_FOLDER / "frs_2023_24", + year=2023, + ) + frs.save(STORAGE_FOLDER / "frs_2023_24.h5") + update_dataset("Create base FRS dataset", "completed") + + # Import imputation functions + from policyengine_uk_data.datasets.imputations import ( + impute_consumption, + impute_wealth, + impute_vat, + impute_income, + impute_capital_gains, + impute_services, + ) + + # Apply imputations with progress tracking + update_dataset("Impute consumption", "processing") + frs = impute_consumption(frs) + update_dataset("Impute consumption", "completed") + + update_dataset("Impute wealth", "processing") + frs = impute_wealth(frs) + update_dataset("Impute wealth", "completed") + + update_dataset("Impute VAT", "processing") + frs = impute_vat(frs) + update_dataset("Impute VAT", "completed") + + update_dataset("Impute public service usage", "processing") + frs = impute_services(frs) + update_dataset("Impute public service usage", "completed") + + update_dataset("Impute income", "processing") + frs = impute_income(frs) + update_dataset("Impute income", "completed") + + update_dataset("Impute capital gains", "processing") + frs = impute_capital_gains(frs) + update_dataset("Impute capital gains", "completed") + + # Uprate dataset + update_dataset("Uprate to 2025", "processing") + frs = uprate_dataset(frs, 2025) + update_dataset("Uprate to 2025", "completed") + + # Calibrate dataset with nested progress + from policyengine_uk_data.datasets.local_areas.constituencies.calibrate import ( + calibrate, + ) + + update_dataset("Calibrate dataset", "processing") + + # Use a separate progress tracker for calibration with nested display + from policyengine_uk_data.utils.calibrate import ( + calibrate_local_areas, + ) + from policyengine_uk_data.datasets.local_areas.constituencies.loss import ( + create_constituency_target_matrix, + create_national_target_matrix, + ) + from policyengine_uk_data.datasets.local_areas.constituencies.calibrate import ( + get_performance, + ) + + # Run calibration with verbose progress + frs_calibrated = calibrate_local_areas( + dataset=frs, + matrix_fn=create_constituency_target_matrix, + national_matrix_fn=create_national_target_matrix, + area_count=650, + weight_file="parliamentary_constituency_weights.h5", + excluded_training_targets=[], + log_csv="calibration_log.csv", + verbose=True, # Enable nested progress display + area_name="Constituency", + get_performance=get_performance, + nested_progress=nested_progress, # Pass the nested progress manager + ) + + update_dataset("Calibrate dataset", "completed") + + # Downrate and save + update_dataset("Downrate to 2023", "processing") + frs_calibrated = uprate_dataset(frs_calibrated, 2023) + update_dataset("Downrate to 2023", "completed") + + update_dataset("Save final dataset", "processing") + frs_calibrated.save(STORAGE_FOLDER / "enhanced_frs_2023_24.h5") + update_dataset("Save final dataset", "completed") + + # Display success message + display_success_panel( + "Dataset creation completed successfully", + details={ + "base_dataset": "frs_2023_24.h5", + "enhanced_dataset": "enhanced_frs_2023_24.h5", + "imputations_applied": "consumption, wealth, VAT, services, income, capital_gains", + "calibration": "national and constituency targets", + }, + ) + + except Exception as e: + display_error_panel( + f"Dataset creation failed: {str(e)}", + suggestions=[ + "Check that all required data files are present in storage folder", + "Verify sufficient disk space for dataset creation", + "Review log files for detailed error information", + ], + ) + raise + + +if __name__ == "__main__": + main() diff --git a/policyengine_uk_data/datasets/frs.py b/policyengine_uk_data/datasets/frs.py new file mode 100644 index 000000000..9153ef1c0 --- /dev/null +++ b/policyengine_uk_data/datasets/frs.py @@ -0,0 +1,815 @@ +""" +Family Resources Survey (FRS) dataset processing for PolicyEngine UK. + +This module processes raw FRS survey data into PolicyEngine UK dataset format, +handling household demographics, income, benefits, and other survey variables. +The FRS is the primary source of UK household survey data used for tax-benefit +modelling and policy analysis. +""" + +from policyengine_uk.data import UKSingleYearDataset +from pathlib import Path +import pandas as pd +import numpy as np +from policyengine_uk_data.utils.datasets import ( + sum_to_entity, + categorical, + sum_from_positive_fields, + sum_positive_variables, + fill_with_mean, + STORAGE_FOLDER, +) + + +def create_frs( + raw_frs_folder: str, + year: int, +) -> UKSingleYearDataset: + """ + Process raw FRS data into PolicyEngine UK dataset format. + + Transforms the Family Resources Survey microdata from raw tab-delimited + files into a structured PolicyEngine UK dataset with person, benefit unit, + and household-level variables mapped to the appropriate tax-benefit system + variables. + + Args: + raw_frs_folder: Path to folder containing raw FRS .tab files. + year: Survey year for the dataset. + + Returns: + UKSingleYearDataset with processed FRS data ready for policy simulation. + """ + raw_folder = Path(raw_frs_folder) + if not raw_folder.exists(): + raise FileNotFoundError(f"Raw folder {raw_folder} does not exist.") + + frs = {} + for file in raw_folder.glob("*.tab"): + table_name = file.stem + # Read and make numeric where possible + df = pd.read_csv(file, sep="\t").apply(pd.to_numeric, errors="coerce") + + # Standardise column names to lower case + df.columns = df.columns.str.lower() + + # Edit ID variables for simplicity + if "sernum" in df.columns: + df.rename(columns={"sernum": "household_id"}, inplace=True) + + if "benunit" in df.columns: + # In the tables, benunit is the index of the benefit unit *within* the household. + df.rename(columns={"benunit": "benunit_id"}, inplace=True) + df["benunit_id"] = ( + df["household_id"] * 1e2 + df["benunit_id"] + ).astype(int) + + if "person" in df.columns: + df.rename(columns={"person": "person_id"}, inplace=True) + df["person_id"] = ( + df["household_id"] * 1e3 + df["person_id"] + ).astype(int) + + frs[table_name] = df + + # Combine adult and child tables for convenience + + frs["person"] = ( + pd.concat([frs["adult"], frs["child"]]).sort_index().fillna(0) + ) + + person = frs["person"] + benunit = frs["benunit"] + household = frs["househol"] + household = household.set_index("household_id") + pension = frs["pension"] + oddjob = frs["oddjob"] + account = frs["accounts"] + job = frs["job"] + benefits = frs["benefits"] + maintenance = frs["maint"] + pen_prov = frs["penprov"] + childcare = frs["chldcare"] + extchild = frs["extchild"] + mortgage = frs["mortgage"] + + pe_person = pd.DataFrame() + pe_benunit = pd.DataFrame() + pe_household = pd.DataFrame() + + # Add primary and foreign keys + pe_person["person_id"] = person.person_id + pe_person["person_benunit_id"] = person.benunit_id + pe_person["person_household_id"] = person.household_id + pe_benunit["benunit_id"] = benunit.benunit_id + pe_household["household_id"] = person.household_id.sort_values().unique() + + # Add grossing weights + pe_household["household_weight"] = household.gross4.values + + # Add basic personal variables + age = person.age80 + person.age + pe_person["age"] = age + pe_person["birth_year"] = np.ones_like(person.age) * (year - age) + # Age fields are AGE80 (top-coded) and AGE in the adult and child tables, respectively. + pe_person["gender"] = np.where(person.sex == 1, "MALE", "FEMALE") + pe_person["hours_worked"] = np.maximum(person.tothours, 0) * 52 + pe_person["is_household_head"] = person.hrpid == 1 + pe_person["is_benunit_head"] = person.uperson == 1 + MARITAL = [ + "MARRIED", + "SINGLE", + "SINGLE", + "WIDOWED", + "SEPARATED", + "DIVORCED", + ] + pe_person["marital_status"] = categorical( + person.marital, 2, range(1, 7), MARITAL + ).fillna("SINGLE") + + # Add education levels + if "fted" in person.columns: + fted = person.fted + else: + fted = person.educft # Renamed in FRS 2022-23 + typeed2 = person.typeed2 + + def determine_education_level(fted_val, typeed2_val, age_val): + # By default, not in education + if fted_val in (2, -1, 0): + return "NOT_IN_EDUCATION" + # In pre-primary + elif typeed2_val == 1: + return "PRE_PRIMARY" + # In primary education + elif ( + typeed2_val in (2, 4) + or (typeed2_val in (3, 8) and age_val < 11) + or ( + typeed2_val == 0 + and fted_val == 1 + and age_val > 5 + and age_val < 11 + ) + ): + return "PRIMARY" + # In lower secondary + elif ( + typeed2_val in (5, 6) + or (typeed2_val in (3, 8) and age_val >= 11 and age_val <= 16) + or (typeed2_val == 0 and fted_val == 1 and age_val <= 16) + ): + return "LOWER_SECONDARY" + # In upper secondary + elif ( + typeed2_val == 7 + or (typeed2_val in (3, 8) and age_val > 16) + or (typeed2_val == 0 and fted_val == 1 and age_val > 16) + ): + return "UPPER_SECONDARY" + # In post-secondary + elif typeed2_val in (7, 8) and age_val >= 19: + return "POST_SECONDARY" + # In tertiary + elif typeed2_val == 9 or ( + typeed2_val == 0 and fted_val == 1 and age_val >= 19 + ): + return "TERTIARY" + else: + return "NOT_IN_EDUCATION" + + # Apply the function to determine education level + pe_person["current_education"] = pd.Series( + [ + determine_education_level(f, t, a) + for f, t, a in zip(fted, typeed2, age) + ], + index=pe_person.index, + ) + + # Add employment status + EMPLOYMENTS = [ + "CHILD", + "FT_EMPLOYED", + "PT_EMPLOYED", + "FT_SELF_EMPLOYED", + "PT_SELF_EMPLOYED", + "UNEMPLOYED", + "RETIRED", + "STUDENT", + "CARER", + "LONG_TERM_DISABLED", + "SHORT_TERM_DISABLED", + ] + pe_person["employment_status"] = categorical( + person.empstati, 1, range(12), EMPLOYMENTS + ).fillna("LONG_TERM_DISABLED") + + REGIONS = [ + "NORTH_EAST", + "NORTH_WEST", + "YORKSHIRE", + "EAST_MIDLANDS", + "WEST_MIDLANDS", + "EAST_OF_ENGLAND", + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "WALES", + "SCOTLAND", + "NORTHERN_IRELAND", + "UNKNOWN", + ] + pe_household["region"] = categorical( + household.gvtregno, 14, [1, 2] + list(range(4, 15)), REGIONS + ).values + TENURES = [ + "RENT_FROM_COUNCIL", + "RENT_FROM_HA", + "RENT_PRIVATELY", + "RENT_PRIVATELY", + "OWNED_OUTRIGHT", + "OWNED_WITH_MORTGAGE", + ] + pe_household["tenure_type"] = categorical( + household.ptentyp2, 3, range(1, 7), TENURES + ).values + frs["num_bedrooms"] = household.bedroom6 + ACCOMMODATIONS = [ + "HOUSE_DETACHED", + "HOUSE_SEMI_DETACHED", + "HOUSE_TERRACED", + "FLAT", + "CONVERTED_HOUSE", + "MOBILE", + "OTHER", + ] + pe_household["accommodation_type"] = categorical( + household.typeacc, 1, range(1, 8), ACCOMMODATIONS + ).values + + # Impute Council Tax + + # Only ~25% of household report Council Tax bills - use + # these to build a model to impute missing values + CT_valid = household.ctannual > 0 + + # Find the mean reported Council Tax bill for a given + # (region, CT band, is-single-person-household) triplet + region = household.gvtregno[CT_valid] + band = household.ctband[CT_valid] + single_person = (household.adulth == 1)[CT_valid] + ctannual = household.ctannual[CT_valid] + + # Build the table + ct_mean = ctannual.groupby( + [region, band, single_person], dropna=False + ).mean() + ct_mean = ct_mean.replace(-1, ct_mean.mean()) + + # For every household consult the table to find the imputed + # Council Tax bill + pairs = household.set_index( + [household.gvtregno, household.ctband, (household.adulth == 1)] + ) + hh_CT_mean = pd.Series(index=pairs.index) + has_mean = pairs.index.isin(ct_mean.index) + hh_CT_mean[has_mean] = ct_mean[pairs.index[has_mean]].values + hh_CT_mean[~has_mean] = 0 + ct_imputed = hh_CT_mean + + # For households which originally reported Council Tax, + # use the reported value. Otherwise, use the imputed value + council_tax = pd.Series( + np.where( + # 2018 FRS uses blanks for missing values, 2019 FRS + # uses -1 for missing values + (household.ctannual < 0) | household.ctannual.isna(), + np.maximum(ct_imputed, 0).values, + household.ctannual, + ) + ) + pe_household["council_tax"] = council_tax.fillna(0) + BANDS = ["A", "B", "C", "D", "E", "F", "G", "H", "I"] + # Band 1 is the most common + pe_household["council_tax_band"] = ( + categorical(household.ctband, 1, range(1, 10), BANDS) + .fillna("D") + .values + ) + # Domestic rates variables are all weeklyised, unlike Council Tax variables (despite the variable name suggesting otherwise) + if year < 2021: + DOMESTIC_RATES_VARIABLE = "rtannual" + else: + DOMESTIC_RATES_VARIABLE = "niratlia" + pe_household["domestic_rates"] = ( + np.select( + [ + household[DOMESTIC_RATES_VARIABLE] >= 0, + household.rt2rebam >= 0, + True, + ], + [ + household[DOMESTIC_RATES_VARIABLE], + household.rt2rebam, + 0, + ], + ) + * 52 + ).astype(float) + + WEEKS_IN_YEAR = 365.25 / 7 + + pe_person["employment_income"] = person.inearns * WEEKS_IN_YEAR + + pension_payment = sum_to_entity( + pension.penpay * (pension.penpay > 0), + pension.person_id, + person.person_id, + ) + pension_tax_paid = sum_to_entity( + (pension.ptamt * ((pension.ptinc == 2) & (pension.ptamt > 0))), + pension.person_id, + person.person_id, + ) + pension_deductions_removed = sum_to_entity( + pension.poamt + * ( + ((pension.poinc == 2) | (pension.penoth == 1)) + & (pension.poamt > 0) + ), + pension.person_id, + person.person_id, + ) + + pe_person["private_pension_income"] = ( + pension_payment + pension_tax_paid + pension_deductions_removed + ) * WEEKS_IN_YEAR + + pe_person["self_employment_income"] = person.seincam2 * WEEKS_IN_YEAR + + INVERTED_BASIC_RATE = 1.25 + + pe_person["tax_free_savings_income"] = ( + sum_to_entity( + account.accint * (account.account == 21), + account.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + taxable_savings_interest = ( + sum_to_entity( + ( + account.accint + * np.where(account.acctax == 1, INVERTED_BASIC_RATE, 1) + ) + * (account.account.isin((1, 3, 5, 27, 28))), + account.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + pe_person["savings_interest_income"] = ( + taxable_savings_interest + pe_person["tax_free_savings_income"].values + ) + pe_person["dividend_income"] = ( + sum_to_entity( + ( + account.accint + * np.where(account.invtax == 1, INVERTED_BASIC_RATE, 1) + ) + * ( + ((account.account == 6) & (account.invtax == 1)) # GGES + | account.account.isin((7, 8)) # Stocks/shares/UITs + ), + account.person_id, + person.index, + ) + * 52 + ) + is_head = person.hrpid == 1 + household_property_income = ( + household.tentyp2.isin((5, 6)) * household.subrent + ) # Owned and subletting + persons_household_property_income = ( + pd.Series( + household_property_income[person.household_id].values, + index=person.person_id, + ) + .fillna(0) + .values + ) + pe_person["property_income"] = ( + np.maximum( + 0, + is_head * persons_household_property_income + + person.cvpay + + person.royyr1, + ) + * WEEKS_IN_YEAR + ) + maintenance_to_self = np.maximum( + pd.Series( + np.where(person.mntus1 == 2, person.mntusam1, person.mntamt1) + ).fillna(0), + 0, + ) + maintenance_from_dwp = person.mntamt2 + pe_person["maintenance_income"] = ( + sum_positive_variables([maintenance_to_self, maintenance_from_dwp]) + * WEEKS_IN_YEAR + ) + + odd_job_income = sum_to_entity( + oddjob.ojamt * (oddjob.ojnow == 1), oddjob.person_id, person.person_id + ) + + MISC_INCOME_FIELDS = [ + "allpay2", + "royyr2", + "royyr3", + "royyr4", + "chamtern", + "chamttst", + ] + + pe_person["miscellaneous_income"] = ( + odd_job_income + sum_from_positive_fields(person, MISC_INCOME_FIELDS) + ) * WEEKS_IN_YEAR + + PRIVATE_TRANSFER_INCOME_FIELDS = [ + "apamt", + "apdamt", + "pareamt", + "allpay2", + "allpay3", + "allpay4", + ] + + pe_person["private_transfer_income"] = ( + sum_from_positive_fields(person, PRIVATE_TRANSFER_INCOME_FIELDS) + * WEEKS_IN_YEAR + ) + + pe_person["lump_sum_income"] = person.redamt + + pe_person["student_loan_repayments"] = person.slrepamt * WEEKS_IN_YEAR + + BENEFIT_CODES = dict( + child_benefit=3, + income_support=19, + housing_benefit=94, + attendance_allowance=12, + dla_sc=1, + dla_m=2, + iidb=15, + carers_allowance=13, + sda=10, + afcs=8, + ssmg=22, + pension_credit=4, + child_tax_credit=91, + working_tax_credit=90, + state_pension=5, + winter_fuel_allowance=62, + incapacity_benefit=17, + universal_credit=95, + pip_m=97, + pip_dl=96, + ) + for benefit, code in BENEFIT_CODES.items(): + pe_person[benefit + "_reported"] = ( + sum_to_entity( + benefits.benamt * (benefits.benefit == code), + benefits.person_id.values, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + + pe_person["jsa_contrib_reported"] = ( + sum_to_entity( + benefits.benamt + * (benefits.var2.isin((1, 3))) + * (benefits.benefit == 14), + benefits.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + pe_person["jsa_income_reported"] = ( + sum_to_entity( + benefits.benamt + * (benefits.var2.isin((2, 4))) + * (benefits.benefit == 14), + benefits.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + pe_person["esa_contrib_reported"] = ( + sum_to_entity( + benefits.benamt + * (benefits.var2.isin((1, 3))) + * (benefits.benefit == 16), + benefits.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + pe_person["esa_income_reported"] = ( + sum_to_entity( + benefits.benamt + * (benefits.var2.isin((2, 4))) + * (benefits.benefit == 16), + benefits.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + + pe_person["bsp_reported"] = ( + sum_to_entity( + benefits.benamt * (benefits.benefit.isin((6, 9))), + benefits.person_id, + person.person_id, + ) + * WEEKS_IN_YEAR + ) + + pe_person["winter_fuel_allowance_reported"] /= WEEKS_IN_YEAR + + pe_person["statutory_sick_pay"] = person.sspadj * WEEKS_IN_YEAR + pe_person["statutory_maternity_pay"] = person.smpadj * WEEKS_IN_YEAR + + pe_person["student_loans"] = np.maximum(person.tuborr, 0) + if "adema" not in person.columns: + person["adema"] = person.eduma + person["ademaamt"] = person.edumaamt + pe_person["adult_ema"] = fill_with_mean(person, "adema", "ademaamt") + pe_person["child_ema"] = fill_with_mean(person, "chema", "chemaamt") + + pe_person["access_fund"] = np.maximum(person.accssamt, 0) * WEEKS_IN_YEAR + + pe_person["education_grants"] = np.maximum( + person[["grtdir1", "grtdir2"]].sum(axis=1), 0 + ) + + pe_person["council_tax_benefit_reported"] = np.maximum( + (person.hrpid == 1) + * pd.Series( + household.ctrebamt[person.household_id.values].values, + index=person.person_id, + ) + .fillna(0) + .values + * WEEKS_IN_YEAR, + 0, + ) + + pe_person["healthy_start_vouchers"] = person.heartval * WEEKS_IN_YEAR + + pe_person["free_school_breakfasts"] = person.fsbval * WEEKS_IN_YEAR + pe_person["free_school_fruit_veg"] = person.fsfvval * WEEKS_IN_YEAR + pe_person["free_school_meals"] = person.fsmval * WEEKS_IN_YEAR + + pe_person["maintenance_expenses"] = ( + pd.Series( + np.where( + maintenance.mrus == 2, maintenance.mruamt, maintenance.mramt + ) + ) + .groupby(maintenance.person_id) + .sum() + .reindex(person.person_id) + .fillna(0) + .values + * WEEKS_IN_YEAR + ) + pe_household["rent"] = household.hhrent.fillna(0).values * WEEKS_IN_YEAR + pe_household["mortgage_interest_repayment"] = ( + household.mortint.fillna(0).values * WEEKS_IN_YEAR + ) + mortgage_capital = np.where( + mortgage.rmort == 1, mortgage.rmamt, mortgage.borramt + ) + mortgage_capital_repayment = sum_to_entity( + mortgage_capital / mortgage.mortend, + mortgage.household_id, + household.index, + ) + pe_household["mortgage_capital_repayment"] = mortgage_capital_repayment + + pe_person["childcare_expenses"] = ( + sum_to_entity( + childcare.chamt + * (childcare.cost == 1) + * (childcare.registrd == 1), + childcare.person_id, + person.person_id, + ) + * 52 + ) + + pe_person["personal_pension_contributions"] = np.maximum( + 0, + sum_to_entity( + pen_prov.penamt[pen_prov.stemppen.isin((5, 6))], + pen_prov.person_id, + person.person_id, + ).clip(0, pen_prov.penamt.quantile(0.95)) + * WEEKS_IN_YEAR, + ) + pe_person["employee_pension_contributions"] = np.maximum( + 0, + sum_to_entity(job.deduc1.fillna(0), job.person_id, person.person_id) + * WEEKS_IN_YEAR, + ) + pe_person["employer_pension_contributions"] = ( + pe_person["employee_pension_contributions"] * 3 + ) # Rough estimate based on aggregates. + + pe_household["housing_service_charges"] = ( + pd.DataFrame( + [ + household[f"chrgamt{i}"] * (household[f"chrgamt{i}"] > 0) + for i in range(1, 10) + ] + ) + .sum() + .values + * WEEKS_IN_YEAR + ) + pe_household["structural_insurance_payments"] = ( + household.struins.values * WEEKS_IN_YEAR + ) + pe_household["water_and_sewerage_charges"] = ( + pd.Series( + np.where( + household.gvtregno == 12, + household.csewamt + household.cwatamtd, + household.watsewrt, + ) + ) + .fillna(0) + .values + * WEEKS_IN_YEAR + ) + + pe_household["external_child_payments"] = sum_to_entity( + extchild.nhhamt * WEEKS_IN_YEAR, + extchild.household_id, + household.index, + ) + + dataset = UKSingleYearDataset( + person=pe_person, + benunit=pe_benunit, + household=pe_household, + fiscal_year=year, + ) + + # Randomly select broad rental market areas from regions. + from policyengine_uk import Microsimulation + + sim = Microsimulation(dataset=dataset) + region = sim.populations["benunit"].household( + "region", dataset.time_period + ) + lha_category = sim.calculate("LHA_category", year) + + brma = np.empty(len(region), dtype=object) + + # Sample from a random BRMA in the region, weighted by the number of observations in each BRMA + lha_list_of_rents = pd.read_csv( + STORAGE_FOLDER / "lha_list_of_rents.csv.gz" + ) + lha_list_of_rents = lha_list_of_rents.copy() + + for possible_region in lha_list_of_rents.region.unique(): + for possible_lha_category in lha_list_of_rents.lha_category.unique(): + lor_mask = (lha_list_of_rents.region == possible_region) & ( + lha_list_of_rents.lha_category == possible_lha_category + ) + mask = (region == possible_region) & ( + lha_category == possible_lha_category + ) + brma[mask] = lha_list_of_rents[lor_mask].brma.sample( + n=len(region[mask]), replace=True + ) + + # Convert benunit-level BRMAs to household-level BRMAs (pick a random one) + + df = pd.DataFrame( + { + "brma": brma, + "household_id": sim.populations["benunit"].household( + "household_id", 2023 + ), + } + ) + + df = df.groupby("household_id").brma.aggregate( + lambda x: x.sample(n=1).iloc[0] + ) + brmas = df[sim.calculate("household_id")].values + + pe_household["brma"] = brmas + + parameters = sim.tax_benefit_system.parameters + benefit = parameters(year).gov.dwp + + pe_person["is_disabled_for_benefits"] = ( + pe_person.dla_sc_reported + + pe_person.dla_m_reported + + pe_person.pip_m_reported + + pe_person.pip_dl_reported + ) > 0 + + THRESHOLD_SAFETY_GAP = 1 * WEEKS_IN_YEAR + + pe_person["is_enhanced_disabled_for_benefits"] = ( + pe_person.dla_sc_reported + > benefit.dla.self_care.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + ) + + # Child Tax Credit Regulations 2002 s. 8 + paragraph_3 = ( + pe_person.dla_sc_reported + >= benefit.dla.self_care.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + ) + paragraph_4 = ( + pe_person.pip_dl_reported + >= benefit.pip.daily_living.enhanced * WEEKS_IN_YEAR + - THRESHOLD_SAFETY_GAP + ) + paragraph_5 = pe_person.afcs_reported > 0 + pe_person["is_severely_disabled_for_benefits"] = ( + paragraph_3 | paragraph_4 | paragraph_5 + ) + + # Add random variables which are for now in policyengine-uk. + + RANDOM_VARIABLES = [ + "would_evade_tv_licence_fee", + "would_claim_pc", + "would_claim_uc", + "would_claim_child_benefit", + "main_residential_property_purchased_is_first_home", + "household_owns_tv", + "is_higher_earner", + "attends_private_school", + ] + + for variable in RANDOM_VARIABLES: + value = sim.calculate(variable).values + entity = sim.tax_benefit_system.variables[variable].entity.key + if entity == "person": + pe_person[variable] = value + elif entity == "household": + pe_household[variable] = value + elif entity == "benunit": + pe_benunit[variable] = value + + # Add Tax-Free Childcare assumptions + + count_benunits = len(pe_benunit) + + extended_would_claim = np.random.random(count_benunits) < 0.812 + tfc_would_claim = np.random.random(count_benunits) < 0.586 + universal_would_claim = np.random.random(count_benunits) < 0.563 + targeted_would_claim = np.random.random(count_benunits) < 0.597 + + # Generate extended childcare hours usage values with mean 15.019 and sd 4.972 + extended_hours_values = np.random.normal(15.019, 4.972, count_benunits) + # Clip values to be between 0 and 30 hours + extended_hours_values = np.clip(extended_hours_values, 0, 30) + + pe_benunit["would_claim_extended_childcare"] = extended_would_claim + pe_benunit["would_claim_tfc"] = tfc_would_claim + pe_benunit["would_claim_universal_childcare"] = universal_would_claim + pe_benunit["would_claim_targeted_childcare"] = targeted_would_claim + + # Add the maximum extended childcare hours usage + pe_benunit["maximum_extended_childcare_hours_usage"] = ( + extended_hours_values + ) + + dataset = UKSingleYearDataset( + person=pe_person, + benunit=pe_benunit, + household=pe_household, + fiscal_year=year, + ) + + return dataset + + +if __name__ == "__main__": + frs = create_frs( + raw_frs_folder=STORAGE_FOLDER / "frs_2022_23", + year=2022, + ) + frs.save(STORAGE_FOLDER / "frs_2022.h5") diff --git a/policyengine_uk_data/datasets/frs/__init__.py b/policyengine_uk_data/datasets/frs/__init__.py deleted file mode 100644 index 67f398ece..000000000 --- a/policyengine_uk_data/datasets/frs/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .dwp_frs import * -from .frs import * -from .extended_frs import * -from .enhanced_frs import * diff --git a/policyengine_uk_data/datasets/frs/dwp_frs.py b/policyengine_uk_data/datasets/frs/dwp_frs.py deleted file mode 100644 index 12e96d462..000000000 --- a/policyengine_uk_data/datasets/frs/dwp_frs.py +++ /dev/null @@ -1,122 +0,0 @@ -from policyengine_core.data import Dataset -from pathlib import Path -import pandas as pd -import warnings -from typing import Type -from policyengine_uk_data.storage import STORAGE_FOLDER - - -class DWP_FRS(Dataset): - data_format = Dataset.TABLES - folder = None - - def generate(self): - """Generate the survey data from the original TAB files. - - Args: - tab_folder (Path): The folder containing the original TAB files. - """ - - tab_folder = self.folder - - if isinstance(tab_folder, str): - tab_folder = Path(tab_folder) - - tab_folder = Path(tab_folder.parent / tab_folder.stem) - # Load the data - tables = {} - for tab_file in tab_folder.glob("*.tab"): - table_name = tab_file.stem - if "frs" in table_name: - continue - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - tables[table_name] = pd.read_csv( - tab_file, delimiter="\t" - ).apply(pd.to_numeric, errors="coerce") - tables[table_name].columns = tables[ - table_name - ].columns.str.upper() - - sernum = ( - "sernum" - if "sernum" in tables[table_name].columns - else "SERNUM" - ) # FRS inconsistently users sernum/SERNUM in different years - - if "PERSON" in tables[table_name].columns: - tables[table_name]["person_id"] = ( - tables[table_name][sernum] * 1e2 - + tables[table_name].BENUNIT * 1e1 - + tables[table_name].PERSON - ).astype(int) - - if "BENUNIT" in tables[table_name].columns: - tables[table_name]["benunit_id"] = ( - tables[table_name][sernum] * 1e2 - + tables[table_name].BENUNIT * 1e1 - ).astype(int) - - if sernum in tables[table_name].columns: - tables[table_name]["household_id"] = ( - tables[table_name][sernum] * 1e2 - ).astype(int) - if table_name in ("adult", "child"): - tables[table_name].set_index( - "person_id", inplace=True, drop=False - ) - elif table_name == "benunit": - tables[table_name].set_index( - "benunit_id", inplace=True, drop=False - ) - elif table_name == "househol": - tables[table_name].set_index( - "household_id", inplace=True, drop=False - ) - tables["benunit"] = tables["benunit"][ - tables["benunit"].benunit_id.isin(tables["adult"].benunit_id) - ] - tables["househol"] = tables["househol"][ - tables["househol"].household_id.isin(tables["adult"].household_id) - ] - - # Save the data - self.save_dataset(tables) - - -class DWP_FRS_2020_21(DWP_FRS): - folder = STORAGE_FOLDER / "frs_2020_21" - name = "dwp_frs_2020_21" - label = "DWP FRS (2020-21)" - file_path = STORAGE_FOLDER / "dwp_frs_2020_21.h5" - time_period = 2020 - - -class DWP_FRS_2021_22(DWP_FRS): - folder = STORAGE_FOLDER / "frs_2021_22" - name = "dwp_frs_2021_22" - label = "DWP FRS (2021-22)" - file_path = STORAGE_FOLDER / "dwp_frs_2021_22.h5" - time_period = 2021 - - -class DWP_FRS_2022_23(DWP_FRS): - folder = STORAGE_FOLDER / "frs_2022_23" - name = "dwp_frs_2022_23" - label = "DWP FRS (2022-23)" - file_path = STORAGE_FOLDER / "dwp_frs_2022_23.h5" - time_period = 2022 - - -class DWP_FRS_2023_24(DWP_FRS): - folder = STORAGE_FOLDER / "frs_2023_24" - name = "dwp_frs_2023_24" - label = "DWP FRS (2023-24)" - file_path = STORAGE_FOLDER / "dwp_frs_2023_24.h5" - time_period = 2023 - - -if __name__ == "__main__": - DWP_FRS_2020_21().generate() - DWP_FRS_2022_23().generate() - DWP_FRS_2023_24().generate() diff --git a/policyengine_uk_data/datasets/frs/enhanced_frs.py b/policyengine_uk_data/datasets/frs/enhanced_frs.py deleted file mode 100644 index b70c35d27..000000000 --- a/policyengine_uk_data/datasets/frs/enhanced_frs.py +++ /dev/null @@ -1,172 +0,0 @@ -from policyengine_core.data import Dataset -from policyengine_uk_data.utils.imputations import * -from policyengine_uk_data.storage import STORAGE_FOLDER -from policyengine_uk_data.datasets.frs.extended_frs import ( - ExtendedFRS_2022_23, - ExtendedFRS_2023_24, -) -from policyengine_uk_data.datasets.frs.frs import FRS_2022_23 -from policyengine_uk_data.utils.loss import create_target_matrix - -from policyengine_uk_data.utils.imputations.capital_gains import ( - impute_cg_to_dataset, -) -from policyengine_uk_data.utils.reweight import reweight - -try: - import torch - from policyengine_uk_data.utils.reweight import reweight -except ImportError: - torch = None - - -class EnhancedFRS(Dataset): - def generate(self): - data = self.input_frs(require=True).load_dataset() - self.save_dataset(data) - - # Capital gains imputation - - impute_cg_to_dataset(self) - data = self.load_dataset() - - self.add_random_variables(data) - self.add_inferred_disability(data) - - data = self.load_dataset() - - self.save_dataset(data) - - def add_random_variables(self, data: dict): - from policyengine_uk import Microsimulation - - simulation = Microsimulation(dataset=self) - RANDOM_VARIABLES = [ - "would_evade_tv_licence_fee", - "would_claim_pc", - "would_claim_uc", - "would_claim_child_benefit", - "main_residential_property_purchased_is_first_home", - "household_owns_tv", - "is_higher_earner", - "attends_private_school", - ] - INPUT_PERIODS = list(range(self.time_period, self.time_period + 10)) - for variable in RANDOM_VARIABLES: - simulation.get_holder(variable).delete_arrays() - for variable in RANDOM_VARIABLES: - value = simulation.calculate(variable, self.time_period).values - data[variable] = {period: value for period in INPUT_PERIODS} - - self.save_dataset(data) - - def add_inferred_disability(self, data: dict): - from policyengine_uk import Microsimulation - - simulation = Microsimulation(dataset=self) - person = simulation.populations["person"] - parameters = simulation.tax_benefit_system.parameters - - INPUT_PERIODS = list(range(self.time_period, self.time_period + 10)) - WEEKS_IN_YEAR = 52 - THRESHOLD_SAFETY_GAP = 10 * WEEKS_IN_YEAR - data["is_disabled_for_benefits"] = {} - data["is_enhanced_disabled_for_benefits"] = {} - data["is_severely_disabled_for_benefits"] = {} - for period in INPUT_PERIODS: - benefit = parameters(period).gov.dwp - data["is_disabled_for_benefits"][period] = ( - person("dla", period) + person("pip", period) > 0 - ) - data["is_enhanced_disabled_for_benefits"][period] = ( - person("dla_sc", period) - > benefit.dla.self_care.higher * WEEKS_IN_YEAR - - THRESHOLD_SAFETY_GAP - ) - # Child Tax Credit Regulations 2002 s. 8 - paragraph_3 = ( - person("dla_sc", period) - >= benefit.dla.self_care.higher * WEEKS_IN_YEAR - - THRESHOLD_SAFETY_GAP - ) - paragraph_4 = ( - person("pip_dl", period) - >= benefit.pip.daily_living.enhanced * WEEKS_IN_YEAR - - THRESHOLD_SAFETY_GAP - ) - paragraph_5 = person("afcs", period) > 0 - data["is_severely_disabled_for_benefits"][period] = ( - sum([paragraph_3, paragraph_4, paragraph_5]) > 0 - ) - - extended_would_claim = ( - np.random.random(len(simulation.calculate("benunit_id"))) < 0.812 - ) - tfc_would_claim = ( - np.random.random(len(simulation.calculate("benunit_id"))) < 0.586 - ) - universal_would_claim = ( - np.random.random(len(simulation.calculate("benunit_id"))) < 0.563 - ) - targeted_would_claim = ( - np.random.random(len(simulation.calculate("benunit_id"))) < 0.597 - ) - - # Generate extended childcare hours usage values with mean 15.019 and sd 4.972 - benunit_count = len(simulation.calculate("benunit_id")) - extended_hours_values = np.random.normal(15.019, 4.972, benunit_count) - # Clip values to be between 0 and 30 hours - extended_hours_values = np.clip(extended_hours_values, 0, 30) - - data["would_claim_extended_childcare"] = { - period: extended_would_claim for period in INPUT_PERIODS - } - data["would_claim_tfc"] = { - period: tfc_would_claim for period in INPUT_PERIODS - } - data["would_claim_universal_childcare"] = { - period: universal_would_claim for period in INPUT_PERIODS - } - data["would_claim_targeted_childcare"] = { - period: targeted_would_claim for period in INPUT_PERIODS - } - - # Add the maximum extended childcare hours usage - data["maximum_extended_childcare_hours_usage"] = { - period: extended_hours_values for period in INPUT_PERIODS - } - - self.save_dataset(data) - - -class ReweightedFRS_2022_23(EnhancedFRS): - name = "reweighted_frs_2022_23" - label = "Reweighted FRS (2022-23)" - file_path = STORAGE_FOLDER / "reweighted_frs_2022_23.h5" - data_format = Dataset.TIME_PERIOD_ARRAYS - input_frs = FRS_2022_23 - time_period = 2022 - end_year = 2022 - - -class EnhancedFRS_2022_23(EnhancedFRS): - name = "enhanced_frs_2022_23" - label = "Enhanced FRS (2022-23)" - file_path = STORAGE_FOLDER / "enhanced_frs_2022_23.h5" - data_format = Dataset.TIME_PERIOD_ARRAYS - input_frs = ExtendedFRS_2022_23 - time_period = 2022 - end_year = 2028 - - -class EnhancedFRS_2023_24(EnhancedFRS): - name = "enhanced_frs_2023_24" - label = "Enhanced FRS (2023-24)" - file_path = STORAGE_FOLDER / "enhanced_frs_2023_24.h5" - data_format = Dataset.TIME_PERIOD_ARRAYS - input_frs = ExtendedFRS_2023_24 - time_period = 2023 - - -if __name__ == "__main__": - EnhancedFRS_2023_24().generate() diff --git a/policyengine_uk_data/datasets/frs/extended_frs.py b/policyengine_uk_data/datasets/frs/extended_frs.py deleted file mode 100644 index debc379fb..000000000 --- a/policyengine_uk_data/datasets/frs/extended_frs.py +++ /dev/null @@ -1,196 +0,0 @@ -from policyengine_core.data import Dataset -from policyengine_uk_data.utils.imputations import * -from policyengine_uk_data.storage import STORAGE_FOLDER -from typing import Type -from policyengine_uk_data.datasets.frs.frs import FRS_2022_23, FRS_2023_24 -from tqdm import tqdm - - -class ExtendedFRS(Dataset): - input_frs: Type[Dataset] - - def generate(self): - from policyengine_uk import Microsimulation - from policyengine_uk_data.utils.qrf import QRF - - create_consumption_model() - create_vat_model() - create_wealth_model() - - consumption = QRF(file_path=STORAGE_FOLDER / "consumption.pkl") - vat = QRF(file_path=STORAGE_FOLDER / "vat.pkl") - wealth = QRF(file_path=STORAGE_FOLDER / "wealth.pkl") - - data = self.input_frs().load_dataset() - simulation = Microsimulation(dataset=self.input_frs) - for imputation_model in tqdm( - [consumption, vat, wealth], desc="Imputing data" - ): - predictors = imputation_model.input_columns - - X_input = simulation.calculate_dataframe( - predictors, map_to="household" - ) - if imputation_model == wealth: - # WAS doesn't sample NI -> put NI households in Wales (closest aggregate) - X_input.loc[ - X_input["region"] == "NORTHERN_IRELAND", "region" - ] = "WALES" - Y_output = imputation_model.predict(X_input) - - for output_variable in Y_output.columns: - values = Y_output[output_variable].values - values[values < 0] = 0 - data[output_variable] = {self.time_period: values} - - # Add public services - - data = add_public_services(data, simulation, self.time_period) - - # Clone the dataset for income imputation - new_data = {} - for variable in data: - new_data[variable] = {} - for time_period in data[variable]: - if "_id" in variable: - # e.g. [1, 2, 3] -> [11, 12, 13, 21, 22, 23] - marker = 10 ** np.ceil( - max(np.log10(data[variable][time_period])) - ) - values = list(data[variable][time_period] + marker) + list( - data[variable][time_period] + marker * 2 - ) - new_data[variable][time_period] = values - elif "_weight" in variable: - new_data[variable][time_period] = list( - data[variable][time_period] - ) + list(data[variable][time_period] * 0) - else: - new_data[variable][time_period] = ( - list(data[variable][time_period]) * 2 - ) - - income_inputs = simulation.calculate_dataframe( - ["age", "gender", "region"] - ) - create_income_model() - - income = QRF(file_path=STORAGE_FOLDER / "income.pkl") - full_imputations = income.predict(income_inputs) - for variable in full_imputations.columns: - # Assign over the second half of the dataset - if variable in new_data.keys(): - new_data[variable][str(self.time_period)] = list( - data[variable][str(self.time_period)] - ) + list(full_imputations[variable].values) - else: - new_data[variable] = { - str(self.time_period): list( - full_imputations[variable].values * 0 - ) - + list(full_imputations[variable].values) - } - - self.save_dataset(new_data) - - -class ExtendedFRS_2022_23(ExtendedFRS): - name = "extended_frs_2022_23" - label = "Extended FRS (2022-23)" - file_path = STORAGE_FOLDER / "extended_frs_2022_23.h5" - data_format = Dataset.TIME_PERIOD_ARRAYS - input_frs = FRS_2022_23 - time_period = 2022 - - -class ExtendedFRS_2023_24(ExtendedFRS): - name = "extended_frs_2023_24" - label = "Extended FRS (2023-24)" - file_path = STORAGE_FOLDER / "extended_frs_2023_24.h5" - data_format = Dataset.TIME_PERIOD_ARRAYS - input_frs = FRS_2023_24 - time_period = 2023 - - -def create_public_services_inputs(sim) -> pd.DataFrame: - variables = [ - "age", - "gender", - "household_weight", - "region", - "household_id", - "is_adult", - "is_child", - "is_SP_age", - "dla", - "pip", - "household_count_people", - "hbai_household_net_income", - "equiv_hbai_household_net_income", - ] - education = sim.calculate("current_education") - - df = sim.calculate_dataframe(variables) - - df["count_primary_education"] = education == "PRIMARY" - df["count_secondary_education"] = education == "LOWER_SECONDARY" - df["count_further_education"] = education.isin( - ["UPPER_SECONDARY", "TERTIARY"] - ) - df["hbai_household_net_income"] = ( - df["hbai_household_net_income"] / df["household_count_people"] - ) - df["equiv_hbai_household_net_income"] = ( - df["equiv_hbai_household_net_income"] / df["household_count_people"] - ) - - return pd.DataFrame(df) - - -def add_public_services(data: dict, simulation, time_period: int): - """ - Add public services data to the dataset. - - Args: - data (dict): The dataset to which public services data will be added. - simulation (Microsimulation): The simulation object used to calculate public services. - time_period (int): The time period for which the data is being added. - - Returns: - dict: The updated dataset with public services data added. - """ - from uk_public_services_imputation import impute_public_services - - public_service_data = create_public_services_inputs(simulation) - - public_services = impute_public_services(public_service_data) - for household_variable in [ - "dfe_education_spending", - "rail_subsidy_spending", - "bus_subsidy_spending", - ]: - data[household_variable] = { - time_period: public_services.groupby("household_id")[ - household_variable - ] - .sum() - .values - } - - for person_variable in [ - "a_and_e_visits", - "admitted_patient_visits", - "outpatient_visits", - "nhs_a_and_e_spending", - "nhs_admitted_patient_spending", - "nhs_outpatient_spending", - ]: - data[person_variable] = { - time_period: public_services[person_variable].values - } - - return data - - -if __name__ == "__main__": - ExtendedFRS_2023_24().generate() diff --git a/policyengine_uk_data/datasets/frs/frs.py b/policyengine_uk_data/datasets/frs/frs.py deleted file mode 100644 index 756796741..000000000 --- a/policyengine_uk_data/datasets/frs/frs.py +++ /dev/null @@ -1,933 +0,0 @@ -from policyengine_core.data import Dataset -import pandas as pd -from pandas import DataFrame -from policyengine_uk_data.utils.datasets import ( - sum_to_entity, - categorical, - sum_from_positive_fields, - sum_positive_variables, - fill_with_mean, - STORAGE_FOLDER, -) -from typing import Dict, List -import numpy as np -from numpy import maximum as max_, where -from typing import Type -import h5py -from policyengine_uk_data.datasets.frs.dwp_frs import * - - -class FRS(Dataset): - name = "frs" - label = "Family Resources Survey" - data_format = Dataset.TIME_PERIOD_ARRAYS - dwp_frs: Type[DWP_FRS] = None - - def generate(self): - dwp_frs_files = self.dwp_frs() - if not dwp_frs_files.file_path.exists(): - raise FileNotFoundError( - f"Raw FRS file {dwp_frs_files.file_path} not found." - ) - else: - dwp_frs_files = dwp_frs_files.load() - frs = {} - TABLES = ( - "adult", - "child", - "accounts", - "benefits", - "job", - "oddjob", - "benunit", - "househol", - "chldcare", - "pension", - "maint", - "mortgage", - "penprov", - "extchild", - ) - ( - adult, - child, - accounts, - benefits, - job, - oddjob, - benunit, - household, - childcare, - pension, - maintenance, - mortgage, - pen_prov, - extchild, - ) = [dwp_frs_files[table] for table in TABLES] - dwp_frs_files.close() - - person = pd.concat([adult, child]).sort_index().fillna(0) - add_id_variables(frs, person, household) - add_personal_variables(frs, person, self.dwp_frs.time_period) - add_benunit_variables(frs, benunit) - add_household_variables(frs, household, self.dwp_frs.time_period) - add_market_income( - frs, person, pension, job, accounts, household, oddjob - ) - add_benefit_income(frs, person, benefits, household) - add_expenses( - frs, - person, - job, - household, - maintenance, - mortgage, - childcare, - pen_prov, - extchild, - ) - for variable in frs: - frs[variable] = {self.dwp_frs.time_period: np.array(frs[variable])} - - self.save_dataset(frs) - - impute_brmas(self, frs) - - self.save_dataset(frs) - - self.add_random_variables(frs) - - def add_inferred_disability(self, data: dict): - from policyengine_uk import Microsimulation - - simulation = Microsimulation(dataset=self) - person = simulation.populations["person"] - parameters = simulation.tax_benefit_system.parameters - - INPUT_PERIODS = list(range(self.time_period, self.time_period + 10)) - WEEKS_IN_YEAR = 52 - THRESHOLD_SAFETY_GAP = 10 * WEEKS_IN_YEAR - period = self.time_period - benefit = parameters(period).gov.dwp - data["is_disabled_for_benefits"] = ( - person("dla", period) + person("pip", period) > 0 - ) - data["is_enhanced_disabled_for_benefits"] = ( - person("dla_sc", period) - > benefit.dla.self_care.higher * WEEKS_IN_YEAR - - THRESHOLD_SAFETY_GAP - ) - # Child Tax Credit Regulations 2002 s. 8 - paragraph_3 = ( - person("dla_sc", period) - >= benefit.dla.self_care.higher * WEEKS_IN_YEAR - - THRESHOLD_SAFETY_GAP - ) - paragraph_4 = ( - person("pip_dl", period) - >= benefit.pip.daily_living.enhanced * WEEKS_IN_YEAR - - THRESHOLD_SAFETY_GAP - ) - paragraph_5 = person("afcs", period) > 0 - data["is_severely_disabled_for_benefits"] = ( - sum([paragraph_3, paragraph_4, paragraph_5]) > 0 - ) - - self.save_dataset(data) - - def add_random_variables(self, frs: dict): - from policyengine_uk import Microsimulation - - simulation = Microsimulation(dataset=self) - RANDOM_VARIABLES = [ - "would_evade_tv_licence_fee", - "would_claim_pc", - "would_claim_uc", - "would_claim_child_benefit", - "main_residential_property_purchased_is_first_home", - "household_owns_tv", - "is_higher_earner", - "attends_private_school", - ] - INPUT_PERIODS = list(range(self.time_period, self.time_period + 10)) - for variable in RANDOM_VARIABLES: - value = simulation.calculate(variable, self.time_period).values - frs[variable] = {period: value for period in INPUT_PERIODS} - - self.save_dataset(frs) - - -class FRS_2020_21(FRS): - dwp_frs = DWP_FRS_2020_21 - name = "frs_2020_21" - label = "FRS (2020-21)" - file_path = STORAGE_FOLDER / "frs_2020_21.h5" - time_period = 2020 - - -class FRS_2021_22(FRS): - dwp_frs = DWP_FRS_2021_22 - name = "frs_2021_22" - label = "FRS (2021-22)" - file_path = STORAGE_FOLDER / "frs_2021_22.h5" - time_period = 2021 - - -class FRS_2022_23(FRS): - dwp_frs = DWP_FRS_2022_23 - name = "frs_2022_23" - label = "FRS (2022-23)" - file_path = STORAGE_FOLDER / "frs_2022_23.h5" - time_period = 2022 - - -class FRS_2023_24(FRS): - dwp_frs = DWP_FRS_2023_24 - name = "frs_2023_24" - label = "FRS (2023-24)" - file_path = STORAGE_FOLDER / "frs_2023_24.h5" - time_period = 2023 - - -def add_id_variables(frs: h5py.File, person: DataFrame, household: DataFrame): - """Adds ID variables and weights. - - Args: - frs (h5py.File) - person (DataFrame) - benunit (DataFrame) - household (DataFrame) - """ - # Add primary and foreign keys - frs["person_id"] = person.index - frs["person_benunit_id"] = person.benunit_id - frs["person_household_id"] = person.household_id - frs["benunit_id"] = person.benunit_id.sort_values().unique() - frs["household_id"] = person.household_id.sort_values().unique() - frs["state_id"] = np.array([1]) - frs["person_state_id"] = np.array([1] * len(person)) - frs["state_weight"] = np.array([1]) - - # Add grossing weights - frs["household_weight"] = household.GROSS4 - - -def add_personal_variables(frs: h5py.File, person: DataFrame, year: int): - """Adds personal variables (age, gender, education). - - Args: - frs (h5py.File) - person (DataFrame) - """ - # Add basic personal variables - age = person.AGE80 + person.AGE - frs["age"] = age - frs["birth_year"] = np.ones_like(person.AGE) * (year - age) - # Age fields are AGE80 (top-coded) and AGE in the adult and child tables, respectively. - frs["gender"] = np.where(person.SEX == 1, "MALE", "FEMALE").astype("S") - frs["hours_worked"] = np.maximum(person.TOTHOURS, 0) * 52 - frs["is_household_head"] = person.HRPID == 1 - frs["is_benunit_head"] = person.UPERSON == 1 - MARITAL = [ - "MARRIED", - "SINGLE", - "SINGLE", - "WIDOWED", - "SEPARATED", - "DIVORCED", - ] - frs["marital_status"] = categorical( - person.MARITAL, 2, range(1, 7), MARITAL - ) - - # Add education levels - if "FTED" in person.columns: - fted = person.FTED - else: - fted = person.EDUCFT # Renamed in FRS 2022-23 - typeed2 = person.TYPEED2 - frs["current_education"] = np.select( - [ - fted.isin((2, -1, 0)), # By default, not in education - typeed2 == 1, # In pre-primary - typeed2.isin((2, 4)) # In primary, or... - | ( - typeed2.isin((3, 8)) & (age < 11) - ) # special or private education (and under 11), or... - | ( - (typeed2 == 0) & (fted == 1) & (age > 5) & (age < 11) - ), # not given, full-time and between 5 and 11 - typeed2.isin((5, 6)) # In secondary, or... - | ( - typeed2.isin((3, 8)) & (age >= 11) & (age <= 16) - ) # special/private and meets age criteria, or... - | ( - (typeed2 == 0) & (fted == 1) & (age <= 16) - ), # not given, full-time and under 17 - typeed2 # Non-advanced further education, or... - == 7 - | ( - typeed2.isin((3, 8)) & (age > 16) - ) # special/private and meets age criteria, or... - | ( - (typeed2 == 0) & (fted == 1) & (age > 16) - ), # not given, full-time and over 16 - typeed2.isin((7, 8)) & (age >= 19), # In post-secondary - typeed2 - == 9 - | ( - (typeed2 == 0) & (fted == 1) & (age >= 19) - ), # In tertiary, or meets age condition - ], - [ - "NOT_IN_EDUCATION", - "PRE_PRIMARY", - "PRIMARY", - "LOWER_SECONDARY", - "UPPER_SECONDARY", - "POST_SECONDARY", - "TERTIARY", - ], - ).astype("S") - - # Add employment status - EMPLOYMENTS = [ - "CHILD", - "FT_EMPLOYED", - "PT_EMPLOYED", - "FT_SELF_EMPLOYED", - "PT_SELF_EMPLOYED", - "UNEMPLOYED", - "RETIRED", - "STUDENT", - "CARER", - "LONG_TERM_DISABLED", - "SHORT_TERM_DISABLED", - ] - frs["employment_status"] = categorical( - person.EMPSTATI, 1, range(12), EMPLOYMENTS - ) - - -def add_household_variables(frs: h5py.File, household: DataFrame, year: int): - """Adds household variables (region, tenure, council tax imputation). - - Args: - frs (h5py.File) - household (DataFrame) - """ - REGIONS = [ - "NORTH_EAST", - "NORTH_WEST", - "YORKSHIRE", - "EAST_MIDLANDS", - "WEST_MIDLANDS", - "EAST_OF_ENGLAND", - "LONDON", - "SOUTH_EAST", - "SOUTH_WEST", - "WALES", - "SCOTLAND", - "NORTHERN_IRELAND", - "UNKNOWN", - ] - frs["region"] = categorical( - household.GVTREGNO, 14, [1, 2] + list(range(4, 15)), REGIONS - ) - TENURES = [ - "RENT_FROM_COUNCIL", - "RENT_FROM_HA", - "RENT_PRIVATELY", - "RENT_PRIVATELY", - "OWNED_OUTRIGHT", - "OWNED_WITH_MORTGAGE", - ] - frs["tenure_type"] = categorical( - household.PTENTYP2, 3, range(1, 7), TENURES - ) - frs["num_bedrooms"] = household.BEDROOM6 - ACCOMMODATIONS = [ - "HOUSE_DETACHED", - "HOUSE_SEMI_DETACHED", - "HOUSE_TERRACED", - "FLAT", - "CONVERTED_HOUSE", - "MOBILE", - "OTHER", - ] - frs["accommodation_type"] = categorical( - household.TYPEACC, 1, range(1, 8), ACCOMMODATIONS - ) - - # Impute Council Tax - - # Only ~25% of household report Council Tax bills - use - # these to build a model to impute missing values - CT_valid = household.CTANNUAL > 0 - - # Find the mean reported Council Tax bill for a given - # (region, CT band, is-single-person-household) triplet - region = household.GVTREGNO[CT_valid] - band = household.CTBAND[CT_valid] - single_person = (household.ADULTH == 1)[CT_valid] - ctannual = household.CTANNUAL[CT_valid] - - # Build the table - CT_mean = ctannual.groupby( - [region, band, single_person], dropna=False - ).mean() - CT_mean = CT_mean.replace(-1, CT_mean.mean()) - - # For every household consult the table to find the imputed - # Council Tax bill - pairs = household.set_index( - [household.GVTREGNO, household.CTBAND, (household.ADULTH == 1)] - ) - hh_CT_mean = pd.Series(index=pairs.index) - has_mean = pairs.index.isin(CT_mean.index) - hh_CT_mean[has_mean] = CT_mean[pairs.index[has_mean]].values - hh_CT_mean[~has_mean] = 0 - CT_imputed = hh_CT_mean - - # For households which originally reported Council Tax, - # use the reported value. Otherwise, use the imputed value - council_tax = pd.Series( - np.where( - # 2018 FRS uses blanks for missing values, 2019 FRS - # uses -1 for missing values - (household.CTANNUAL < 0) | household.CTANNUAL.isna(), - max_(CT_imputed, 0).values, - household.CTANNUAL, - ) - ) - frs["council_tax"] = council_tax.fillna(0) - BANDS = ["A", "B", "C", "D", "E", "F", "G", "H", "I"] - # Band 1 is the most common - frs["council_tax_band"] = categorical( - household.CTBAND, 1, range(1, 10), BANDS - ) - # Domestic rates variables are all weeklyised, unlike Council Tax variables (despite the variable name suggesting otherwise) - if year < 2021: - DOMESTIC_RATES_VARIABLE = "RTANNUAL" - else: - DOMESTIC_RATES_VARIABLE = "NIRATLIA" - frs["domestic_rates"] = ( - np.select( - [ - household[DOMESTIC_RATES_VARIABLE] >= 0, - household.RT2REBAM >= 0, - True, - ], - [ - household[DOMESTIC_RATES_VARIABLE], - household.RT2REBAM, - 0, - ], - ) - * 52 - ).astype(float) - - -def add_market_income( - frs: h5py.File, - person: DataFrame, - pension: DataFrame, - job: DataFrame, - account: DataFrame, - household: DataFrame, - oddjob: DataFrame, -): - """Adds income variables (non-benefit). - - Args: - frs (h5py.File) - person (DataFrame) - pension (DataFrame) - job (DataFrame) - account (DataFrame) - household (DataFrame) - oddjob (DataFrame) - """ - frs["employment_income"] = person.INEARNS * 52 - - pension_payment = sum_to_entity( - pension.PENPAY * (pension.PENPAY > 0), pension.person_id, person.index - ) - pension_tax_paid = sum_to_entity( - (pension.PTAMT * ((pension.PTINC == 2) & (pension.PTAMT > 0))), - pension.person_id, - person.index, - ) - pension_deductions_removed = sum_to_entity( - pension.POAMT - * ( - ((pension.POINC == 2) | (pension.PENOTH == 1)) - & (pension.POAMT > 0) - ), - pension.person_id, - person.index, - ) - - frs["private_pension_income"] = ( - pension_payment + pension_tax_paid + pension_deductions_removed - ) * 52 - - frs["self_employment_income"] = person.SEINCAM2 * 52 - - INVERTED_BASIC_RATE = 1.25 - - frs["tax_free_savings_income"] = ( - sum_to_entity( - account.ACCINT * (account.ACCOUNT == 21), - account.person_id, - person.index, - ) - * 52 - ) - taxable_savings_interest = ( - sum_to_entity( - ( - account.ACCINT - * np.where(account.ACCTAX == 1, INVERTED_BASIC_RATE, 1) - ) - * (account.ACCOUNT.isin((1, 3, 5, 27, 28))), - account.person_id, - person.index, - ) - * 52 - ) - frs["savings_interest_income"] = ( - taxable_savings_interest + frs["tax_free_savings_income"] - ) - frs["dividend_income"] = ( - sum_to_entity( - ( - account.ACCINT - * np.where(account.INVTAX == 1, INVERTED_BASIC_RATE, 1) - ) - * ( - ((account.ACCOUNT == 6) & (account.INVTAX == 1)) # GGES - | account.ACCOUNT.isin((7, 8)) # Stocks/shares/UITs - ), - account.person_id, - person.index, - ) - * 52 - ) - is_head = person.HRPID == 1 - household_property_income = ( - household.TENTYP2.isin((5, 6)) * household.SUBRENT - ) # Owned and subletting - persons_household_property_income = pd.Series( - household_property_income[person.household_id].values, - index=person.index, - ).fillna(0) - frs["property_income"] = ( - max_( - 0, - is_head * persons_household_property_income - + person.CVPAY - + person.ROYYR1, - ) - * 52 - ) - maintenance_to_self = max_( - pd.Series( - where(person.MNTUS1 == 2, person.MNTUSAM1, person.MNTAMT1) - ).fillna(0), - 0, - ) - maintenance_from_DWP = person.MNTAMT2 - frs["maintenance_income"] = ( - sum_positive_variables([maintenance_to_self, maintenance_from_DWP]) - * 52 - ) - - odd_job_income = sum_to_entity( - oddjob.OJAMT * (oddjob.OJNOW == 1), oddjob.person_id, person.index - ) - - MISC_INCOME_FIELDS = [ - "ALLPAY2", - "ROYYR2", - "ROYYR3", - "ROYYR4", - "CHAMTERN", - "CHAMTTST", - ] - - frs["miscellaneous_income"] = ( - odd_job_income + sum_from_positive_fields(person, MISC_INCOME_FIELDS) - ) * 52 - - PRIVATE_TRANSFER_INCOME_FIELDS = [ - "APAMT", - "APDAMT", - "PAREAMT", - "ALLPAY1", - "ALLPAY3", - "ALLPAY4", - ] - - frs["private_transfer_income"] = ( - sum_from_positive_fields(person, PRIVATE_TRANSFER_INCOME_FIELDS) * 52 - ) - - frs["lump_sum_income"] = person.REDAMT - - frs["student_loan_repayments"] = person.SLREPAMT * 52 - - -def sum_from_positive_fields( - table: pd.DataFrame, fields: List[str] -) -> np.array: - """Sum from fields in table, ignoring negative values. - - Args: - table (DataFrame) - fields (List[str]) - - Returns: - np.array - """ - return np.where( - table[fields].sum(axis=1) > 0, table[fields].sum(axis=1), 0 - ) - - -def sum_positive_variables(variables: List[str]) -> np.array: - """Sum positive variables. - - Args: - variables (List[str]) - - Returns: - np.array - """ - return sum([np.where(variable > 0, variable, 0) for variable in variables]) - - -def fill_with_mean( - table: pd.DataFrame, code: str, amount: str, multiplier: float = 52 -) -> np.array: - """Fills missing values in a table with the mean of the column. - - Args: - table (DataFrame): Table to fill. - code (str): Column signifying existence. - amount (str): Column with values. - multiplier (float): Multiplier to apply to amount. - - Returns: - np.array: Filled values. - """ - needs_fill = (table[code] == 1) & (table[amount] < 0) - has_value = (table[code] == 1) & (table[amount] >= 0) - fill_mean = table[amount][has_value].mean() - filled_values = np.where(needs_fill, fill_mean, table[amount]) - return np.maximum(filled_values, 0) * multiplier - - -def add_benefit_income( - frs: h5py.File, - person: DataFrame, - benefits: DataFrame, - household: DataFrame, -): - """Adds benefit variables. - - Args: - frs (h5py.File) - person (DataFrame) - benefits (DataFrame) - household (DataFrame) - """ - BENEFIT_CODES = dict( - child_benefit=3, - income_support=19, - housing_benefit=94, - attendance_allowance=12, - dla_sc=1, - dla_m=2, - iidb=15, - carers_allowance=13, - sda=10, - afcs=8, - ssmg=22, - pension_credit=4, - child_tax_credit=91, - working_tax_credit=90, - state_pension=5, - winter_fuel_allowance=62, - incapacity_benefit=17, - universal_credit=95, - pip_m=97, - pip_dl=96, - ) - - for benefit, code in BENEFIT_CODES.items(): - frs[benefit + "_reported"] = ( - sum_to_entity( - benefits.BENAMT * (benefits.BENEFIT == code), - benefits.person_id, - person.index, - ) - * 52 - ) - - frs["jsa_contrib_reported"] = ( - sum_to_entity( - benefits.BENAMT - * (benefits.VAR2.isin((1, 3))) - * (benefits.BENEFIT == 14), - benefits.person_id, - person.index, - ) - * 52 - ) - frs["jsa_income_reported"] = ( - sum_to_entity( - benefits.BENAMT - * (benefits.VAR2.isin((2, 4))) - * (benefits.BENEFIT == 14), - benefits.person_id, - person.index, - ) - * 52 - ) - frs["esa_contrib_reported"] = ( - sum_to_entity( - benefits.BENAMT - * (benefits.VAR2.isin((1, 3))) - * (benefits.BENEFIT == 16), - benefits.person_id, - person.index, - ) - * 52 - ) - frs["esa_income_reported"] = ( - sum_to_entity( - benefits.BENAMT - * (benefits.VAR2.isin((2, 4))) - * (benefits.BENEFIT == 16), - benefits.person_id, - person.index, - ) - * 52 - ) - - frs["bsp_reported"] = ( - sum_to_entity( - benefits.BENAMT * (benefits.BENEFIT.isin((6, 9))), - benefits.person_id, - person.index, - ) - * 52 - ) - - frs["winter_fuel_allowance_reported"] = ( - np.array(frs["winter_fuel_allowance_reported"]) / 52 - ) # This is not weeklyised by default (paid once per year) - - frs["statutory_sick_pay"] = person.SSPADJ * 52 - frs["statutory_maternity_pay"] = person.SMPADJ * 52 - - frs["student_loans"] = np.maximum(person.TUBORR, 0) - if "ADEMA" not in person.columns: - person["ADEMA"] = person.EDUMA - person["ADEMAAMT"] = person.EDUMAAMT - frs["adult_ema"] = fill_with_mean(person, "ADEMA", "ADEMAAMT") - frs["child_ema"] = fill_with_mean(person, "CHEMA", "CHEMAAMT") - - frs["access_fund"] = np.maximum(person.ACCSSAMT, 0) * 52 - - frs["education_grants"] = np.maximum( - person[["GRTDIR1", "GRTDIR2"]].sum(axis=1), 0 - ) - - frs["council_tax_benefit_reported"] = np.maximum( - (person.HRPID == 1) - * pd.Series( - household.CTREBAMT[person.household_id].values, index=person.index - ).fillna(0) - * 52, - 0, - ) - - frs["healthy_start_vouchers"] = person.HEARTVAL * 52 - - WEEKS_IN_YEAR = 52 - - frs["free_school_breakfasts"] = person.FSBVAL * WEEKS_IN_YEAR - frs["free_school_fruit_veg"] = person.FSFVVAL * WEEKS_IN_YEAR - frs["free_school_meals"] = person.FSMVAL * WEEKS_IN_YEAR - - -def add_expenses( - frs: h5py.File, - person: DataFrame, - job: DataFrame, - household: DataFrame, - maintenance: DataFrame, - mortgage: DataFrame, - childcare: DataFrame, - pen_prov: DataFrame, - extchild: DataFrame, -): - """Adds expense variables - - Args: - frs (h5py.File) - person (DataFrame) - household (DataFrame) - maintenance (DataFrame) - mortgage (DataFrame) - childcare (DataFrame) - pen_prov (DataFrame) - """ - frs["maintenance_expenses"] = ( - pd.Series( - np.where( - maintenance.MRUS == 2, maintenance.MRUAMT, maintenance.MRAMT - ) - ) - .groupby(maintenance.person_id) - .sum() - .reindex(person.index) - .fillna(0) - * 52 - ) - - frs["housing_costs"] = ( - np.where( - household.GVTREGNO != 13, household.GBHSCOST, household.NIHSCOST - ) - * 52 - ) - frs["rent"] = household.HHRENT.fillna(0) * 52 - frs["mortgage_interest_repayment"] = household.MORTINT.fillna(0) * 52 - mortgage_capital = np.where( - mortgage.RMORT == 1, mortgage.RMAMT, mortgage.BORRAMT - ) - mortgage_capital_repayment = sum_to_entity( - mortgage_capital / mortgage.MORTEND, - mortgage.household_id, - household.index, - ) - frs["mortgage_capital_repayment"] = mortgage_capital_repayment - - frs["childcare_expenses"] = ( - sum_to_entity( - childcare.CHAMT - * (childcare.COST == 1) - * (childcare.REGISTRD == 1), - childcare.person_id, - person.index, - ) - * 52 - ) - - frs["personal_pension_contributions"] = max_( - 0, - sum_to_entity( - pen_prov.PENAMT[pen_prov.STEMPPEN.isin((5, 6))], - pen_prov.person_id, - person.index, - ).clip(0, pen_prov.PENAMT.quantile(0.95)) - * 52, - ) - frs["employee_pension_contributions"] = max_( - 0, - sum_to_entity(job.DEDUC1.fillna(0), job.person_id, person.index) * 52, - ) - frs["employer_pension_contributions"] = ( - frs["employee_pension_contributions"] * 3 - ) # Rough estimate based on aggregates. - - frs["housing_service_charges"] = ( - pd.DataFrame( - [ - household[f"CHRGAMT{i}"] * (household[f"CHRGAMT{i}"] > 0) - for i in range(1, 10) - ] - ).sum() - * 52 - ) - frs["structural_insurance_payments"] = household.STRUINS * 52 - frs["water_and_sewerage_charges"] = ( - pd.Series( - np.where( - household.GVTREGNO == 12, - household.CSEWAMT + household.CWATAMTD, - household.WATSEWRT, - ) - ).fillna(0) - * 52 - ) - - frs["external_child_payments"] = sum_to_entity( - extchild.NHHAMT * 52, - extchild.household_id, - household.index, - ) - - -def add_benunit_variables(frs: h5py.File, benunit: DataFrame): - pass - - -def impute_brmas(dataset, frs): - # Randomly select broad rental market areas from regions. - from policyengine_uk import Microsimulation - - sim = Microsimulation(dataset=dataset) - region = ( - sim.populations["benunit"] - .household("region", dataset.time_period) - .decode_to_str() - ) - lha_category = sim.calculate("LHA_category") - - brma = np.empty(len(region), dtype=object) - - # Sample from a random BRMA in the region, weighted by the number of observations in each BRMA - lha_list_of_rents = pd.read_csv( - STORAGE_FOLDER / "lha_list_of_rents.csv.gz" - ) - lha_list_of_rents = lha_list_of_rents.copy() - - for possible_region in lha_list_of_rents.region.unique(): - for possible_lha_category in lha_list_of_rents.lha_category.unique(): - lor_mask = (lha_list_of_rents.region == possible_region) & ( - lha_list_of_rents.lha_category == possible_lha_category - ) - mask = (region == possible_region) & ( - lha_category == possible_lha_category - ) - brma[mask] = lha_list_of_rents[lor_mask].brma.sample( - n=len(region[mask]), replace=True - ) - - # Convert benunit-level BRMAs to household-level BRMAs (pick a random one) - - df = pd.DataFrame( - { - "brma": brma, - "household_id": sim.populations["benunit"].household( - "household_id", 2023 - ), - } - ) - - df = df.groupby("household_id").brma.aggregate( - lambda x: x.sample(n=1).iloc[0] - ) - brmas = df[sim.calculate("household_id")].values - - frs["brma"] = {dataset.time_period: brmas} - - -if __name__ == "__main__": - FRS_2020_21().generate() - FRS_2022_23().generate() - FRS_2023_24().generate() diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/calibrate.py b/policyengine_uk_data/datasets/frs/local_areas/constituencies/calibrate.py deleted file mode 100644 index b57cbaa9f..000000000 --- a/policyengine_uk_data/datasets/frs/local_areas/constituencies/calibrate.py +++ /dev/null @@ -1,283 +0,0 @@ -import torch -from policyengine_uk import Microsimulation -import pandas as pd -import numpy as np -from tqdm import tqdm -import h5py -import os -import argparse - -# Fill in missing constituencies with average column values -import pandas as pd -import numpy as np - -from policyengine_uk_data.datasets.frs.local_areas.constituencies.loss import ( - create_constituency_target_matrix, - create_national_target_matrix, -) -from policyengine_uk_data.datasets.frs.local_areas.constituencies.boundary_changes.mapping_matrix import ( - mapping_matrix, -) -from pathlib import Path -from policyengine_uk_data.storage import STORAGE_FOLDER -from policyengine_uk_data.datasets import EnhancedFRS_2023_24 - -FOLDER = Path(__file__).parent - - -def calibrate( - epochs: int = 128, - excluded_training_targets=[], - log_csv="calibration_log.csv", - overwrite_efrs=True, -): - matrix_, y_, country_mask = create_constituency_target_matrix( - EnhancedFRS_2023_24, 2025 - ) - - m_national_, y_national_ = create_national_target_matrix( - EnhancedFRS_2023_24, 2025 - ) - - sim = Microsimulation(dataset=EnhancedFRS_2023_24) - - COUNT_CONSTITUENCIES = 650 - - # Weights - 650 x 100180 - original_weights = np.log( - sim.calculate("household_weight", 2025).values / COUNT_CONSTITUENCIES - + np.random.random(len(sim.calculate("household_weight", 2025).values)) - * 0.01 - ) - weights = torch.tensor( - np.ones((COUNT_CONSTITUENCIES, len(original_weights))) - * original_weights, - dtype=torch.float32, - requires_grad=True, - ) - validation_targets_c = matrix_.columns.isin(excluded_training_targets) - validation_targets_n = m_national_.columns.isin(excluded_training_targets) - if len(excluded_training_targets) > 0: - dropout_targets = True - else: - dropout_targets = False - - metrics = torch.tensor(matrix_.values, dtype=torch.float32) - y = torch.tensor(y_.values, dtype=torch.float32) - matrix_national = torch.tensor(m_national_.values, dtype=torch.float32) - y_national = torch.tensor(y_national_.values, dtype=torch.float32) - r = torch.tensor(country_mask, dtype=torch.float32) - - def loss(w, validation: bool = False): - pred_c = (w.unsqueeze(-1) * metrics.unsqueeze(0)).sum(dim=1) - if dropout_targets: - if validation: - mask = validation_targets_c - else: - mask = ~validation_targets_c - pred_c = pred_c[:, mask] - mse_c = torch.mean((pred_c / (1 + y[:, mask]) - 1) ** 2) - else: - mse_c = torch.mean((pred_c / (1 + y) - 1) ** 2) - - pred_n = (w.sum(axis=0) * matrix_national.T).sum(axis=1) - if dropout_targets: - if validation: - mask = validation_targets_n - else: - mask = ~validation_targets_n - pred_n = pred_n[mask] - mse_n = torch.mean((pred_n / (1 + y_national[mask]) - 1) ** 2) - else: - mse_n = torch.mean((pred_n / (1 + y_national) - 1) ** 2) - - return mse_c + mse_n - - def pct_close(w, t=0.1, constituency=True, national=True): - # Return the percentage of metrics that are within t% of the target - numerator = 0 - denominator = 0 - pred_c = (w.unsqueeze(-1) * metrics.unsqueeze(0)).sum(dim=1) - e_c = torch.sum(torch.abs((pred_c / (1 + y) - 1)) < t).item() - c_c = pred_c.shape[0] * pred_c.shape[1] - - if constituency: - numerator += e_c - denominator += c_c - - pred_n = (w.sum(axis=0) * matrix_national.T).sum(axis=1) - e_n = torch.sum(torch.abs((pred_n / (1 + y_national) - 1)) < t).item() - c_n = pred_n.shape[0] - - if national: - numerator += e_n - denominator += c_n - - return numerator / denominator - - def dropout_weights(weights, p): - if p == 0: - return weights - # Replace p% of the weights with the mean value of the rest of them - mask = torch.rand_like(weights) < p - mean = weights[~mask].mean() - masked_weights = weights.clone() - masked_weights[mask] = mean - return masked_weights - - optimizer = torch.optim.Adam([weights], lr=1e-1) - - desc = range(128) if os.environ.get("DATA_LITE") else range(epochs) - final_weights = (torch.exp(weights) * r).detach().numpy() - performance = pd.DataFrame() - - for epoch in desc: - optimizer.zero_grad() - weights_ = torch.exp(dropout_weights(weights, 0.05)) * r - l = loss(weights_) - c_close = pct_close(weights_, constituency=True, national=False, t=0.1) - n_close = pct_close(weights_, constituency=False, national=True, t=0.1) - if epoch % 1 == 0: - if dropout_targets: - validation_loss = loss(weights_, validation=True) - print( - f"Training loss: {l.item():,.3f}, Validation loss: {validation_loss.item():,.3f}, Epoch: {epoch}, " - f"Constituency<10%: {c_close:.1%}, National<10%: {n_close:.1%}" - ) - else: - print( - f"Loss: {l.item()}, Epoch: {epoch}, Constituency<10%: {c_close:.1%}, National<10%: {n_close:.1%}" - ) - if epoch % 10 == 0: - final_weights = (torch.exp(weights) * r).detach().numpy() - - performance_step = get_performance( - final_weights, - matrix_, - y_, - m_national_, - y_national_, - excluded_training_targets, - ) - performance_step["epoch"] = epoch - performance_step["loss"] = performance_step.rel_abs_error**2 - performance_step["target_name"] = [ - f"{area}/{metric}" - for area, metric in zip( - performance_step.name, performance_step.metric - ) - ] - performance = pd.concat( - [performance, performance_step], ignore_index=True - ) - - if log_csv: - performance.to_csv(log_csv, index=False) - - with h5py.File( - STORAGE_FOLDER / "parliamentary_constituency_weights.h5", "w" - ) as f: - f.create_dataset("2025", data=final_weights) - - if overwrite_efrs: - with h5py.File( - STORAGE_FOLDER / "enhanced_frs_2023_24.h5", "r+" - ) as f: - if "household_weight/2023" in f: - del f["household_weight/2023"] - f.create_dataset( - "household_weight/2023", - data=final_weights.sum(axis=0) / 1.021, - ) - l.backward() - optimizer.step() - - return final_weights - - -def get_performance(weights, m_c, y_c, m_n, y_n, excluded_targets): - constituency_target_matrix, constituency_actuals = m_c, y_c - national_target_matrix, national_actuals = m_n, y_n - constituencies = pd.read_csv(STORAGE_FOLDER / "constituencies_2024.csv") - constituency_wide = weights @ constituency_target_matrix - constituency_wide.index = constituencies.code.values - constituency_wide["name"] = constituencies.name.values - - constituency_results = pd.melt( - constituency_wide.reset_index(), - id_vars=["index", "name"], - var_name="variable", - value_name="value", - ) - - constituency_actuals.index = constituencies.code.values - constituency_actuals["name"] = constituencies.name.values - constituency_actuals_long = pd.melt( - constituency_actuals.reset_index(), - id_vars=["index", "name"], - var_name="variable", - value_name="value", - ) - - constituency_target_validation = pd.merge( - constituency_results, - constituency_actuals_long, - on=["index", "variable"], - suffixes=("_target", "_actual"), - ) - constituency_target_validation.drop("name_actual", axis=1, inplace=True) - constituency_target_validation.columns = [ - "index", - "name", - "metric", - "estimate", - "target", - ] - - constituency_target_validation["error"] = ( - constituency_target_validation["estimate"] - - constituency_target_validation["target"] - ) - constituency_target_validation["abs_error"] = ( - constituency_target_validation["error"].abs() - ) - constituency_target_validation["rel_abs_error"] = ( - constituency_target_validation["abs_error"] - / constituency_target_validation["target"] - ) - - national_performance = weights.sum(axis=0) @ national_target_matrix - national_target_validation = pd.DataFrame( - { - "metric": national_performance.index, - "estimate": national_performance.values, - } - ) - national_target_validation["target"] = national_actuals.values - - national_target_validation["error"] = ( - national_target_validation["estimate"] - - national_target_validation["target"] - ) - national_target_validation["abs_error"] = national_target_validation[ - "error" - ].abs() - national_target_validation["rel_abs_error"] = ( - national_target_validation["abs_error"] - / national_target_validation["target"] - ) - - df = pd.concat( - [ - constituency_target_validation, - national_target_validation.assign(name="UK", index=0), - ] - ).reset_index(drop=True) - - df["validation"] = df.metric.isin(excluded_targets) - - return df - - -if __name__ == "__main__": - calibrate() diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/calibrate.py b/policyengine_uk_data/datasets/frs/local_areas/local_authorities/calibrate.py deleted file mode 100644 index dd0722dfe..000000000 --- a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/calibrate.py +++ /dev/null @@ -1,123 +0,0 @@ -import torch -from policyengine_uk import Microsimulation -import pandas as pd -import numpy as np -from tqdm import tqdm -import h5py -import os -from policyengine_uk_data.storage import STORAGE_FOLDER - - -from policyengine_uk_data.datasets.frs.local_areas.local_authorities.loss import ( - create_local_authority_target_matrix, - create_national_target_matrix, -) -from policyengine_uk_data.datasets import EnhancedFRS_2023_24 - -DEVICE = "cpu" - - -def calibrate(): - matrix, y, r = create_local_authority_target_matrix( - EnhancedFRS_2023_24, 2025 - ) - - m_national, y_national = create_national_target_matrix( - EnhancedFRS_2023_24, 2025 - ) - - sim = Microsimulation(dataset=EnhancedFRS_2023_24) - - count_local_authority = 360 - - # Weights - 360 x 100180 - original_weights = np.log( - sim.calculate("household_weight", 2025).values / count_local_authority - + np.random.random(len(sim.calculate("household_weight", 2025).values)) - * 0.01 - ) - weights = torch.tensor( - np.ones((count_local_authority, len(original_weights))) - * original_weights, - dtype=torch.float32, - device=DEVICE, - requires_grad=True, - ) - metrics = torch.tensor(matrix.values, dtype=torch.float32, device=DEVICE) - y = torch.tensor(y.values, dtype=torch.float32, device=DEVICE) - matrix_national = torch.tensor( - m_national.values, dtype=torch.float32, device=DEVICE - ) - y_national = torch.tensor( - y_national.values, dtype=torch.float32, device=DEVICE - ) - r = torch.tensor(r, dtype=torch.float32, device=DEVICE) - - def loss(w): - pred_c = (w.unsqueeze(-1) * metrics.unsqueeze(0)).sum(dim=1) - mse_c = torch.mean((pred_c / (1 + y) - 1) ** 2) - - pred_n = (w.sum(axis=0) * matrix_national.T).sum(axis=1) - mse_n = torch.mean((pred_n / (1 + y_national) - 1) ** 2) - - return mse_c + mse_n - - def pct_close(w, t=0.1, la=True, national=True): - # Return the percentage of metrics that are within t% of the target - numerator = 0 - denominator = 0 - pred_la = (w.unsqueeze(-1) * metrics.unsqueeze(0)).sum(dim=1) - e_la = torch.sum(torch.abs((pred_la / (1 + y) - 1)) < t).item() - c_la = pred_la.shape[0] * pred_la.shape[1] - - if la: - numerator += e_la - denominator += c_la - - pred_n = (w.sum(axis=0) * matrix_national.T).sum(axis=1) - e_n = torch.sum(torch.abs((pred_n / (1 + y_national) - 1)) < t).item() - c_n = pred_n.shape[0] - - if national: - numerator += e_n - denominator += c_n - - return numerator / denominator - - def dropout_weights(weights, p): - if p == 0: - return weights - # Replace p% of the weights with the mean value of the rest of them - mask = torch.rand_like(weights) < p - mean = weights[~mask].mean() - masked_weights = weights.clone() - masked_weights[mask] = mean - return masked_weights - - optimizer = torch.optim.Adam([weights], lr=1e-1) - - desc = range(32) if os.environ.get("DATA_LITE") else range(128) - - for epoch in desc: - optimizer.zero_grad() - weights_ = torch.exp(dropout_weights(weights, 0.05)) * r - l = loss(weights_) - l.backward() - optimizer.step() - c_close = pct_close(weights_, la=True, national=False) - n_close = pct_close(weights_, la=False, national=True) - if epoch % 1 == 0: - print( - f"Loss: {l.item()}, Epoch: {epoch}, Local Authority<10%: {c_close:.1%}, National<10%: {n_close:.1%}" - ) - if epoch % 10 == 0: - final_weights = (torch.exp(weights) * r).detach().cpu().numpy() - - with h5py.File( - STORAGE_FOLDER / "local_authority_weights.h5", "w" - ) as f: - f.create_dataset("2025", data=final_weights) - - -if __name__ == "__main__": - calibrate() diff --git a/policyengine_uk_data/utils/imputations/README.md b/policyengine_uk_data/datasets/imputations/README.md similarity index 100% rename from policyengine_uk_data/utils/imputations/README.md rename to policyengine_uk_data/datasets/imputations/README.md diff --git a/policyengine_uk_data/utils/imputations/__init__.py b/policyengine_uk_data/datasets/imputations/__init__.py similarity index 57% rename from policyengine_uk_data/utils/imputations/__init__.py rename to policyengine_uk_data/datasets/imputations/__init__.py index f38fe775e..22e9cede0 100644 --- a/policyengine_uk_data/utils/imputations/__init__.py +++ b/policyengine_uk_data/datasets/imputations/__init__.py @@ -2,3 +2,5 @@ from .vat import * from .wealth import * from .income import * +from .capital_gains import * +from .services import impute_services diff --git a/policyengine_uk_data/utils/imputations/capital_gains.py b/policyengine_uk_data/datasets/imputations/capital_gains.py similarity index 66% rename from policyengine_uk_data/utils/imputations/capital_gains.py rename to policyengine_uk_data/datasets/imputations/capital_gains.py index 970b75586..7408af5a6 100644 --- a/policyengine_uk_data/utils/imputations/capital_gains.py +++ b/policyengine_uk_data/datasets/imputations/capital_gains.py @@ -1,22 +1,21 @@ import pandas as pd import numpy as np from policyengine_core.data import Dataset +from policyengine_uk_data.utils.stack import stack_datasets # Fit a spline to each income band's percentiles -try: - from scipy.interpolate import UnivariateSpline -except ImportError: - pass +from scipy.interpolate import UnivariateSpline + from policyengine_uk_data.storage import STORAGE_FOLDER from tqdm import tqdm import copy -try: - import torch - from torch.optim import Adam -except ImportError: - pass +import torch +from torch.optim import Adam from tqdm import tqdm +from policyengine_uk.data import UKSingleYearDataset +import logging +from policyengine_uk_data.utils.subsample import subsample_dataset capital_gains = pd.read_csv( STORAGE_FOLDER / "capital_gains_distribution_advani_summers.csv.gz" @@ -25,16 +24,21 @@ capital_gains.minimum_total_income.shift(-1).fillna(np.inf) ) +# Silence verbose logging +logging.getLogger("root").setLevel(logging.WARNING) + -def impute_capital_gains(dataset, time_period: int): +def impute_cg_to_doubled_dataset( + dataset: UKSingleYearDataset, +) -> tuple[np.ndarray, np.ndarray]: """Assumes that the capital gains distribution is the same for all years.""" from policyengine_uk import Microsimulation from policyengine_uk.system import system sim = Microsimulation(dataset=dataset) - ti = sim.calculate("total_income", time_period) - household_weight = sim.calculate("household_weight", time_period).values + ti = sim.calculate("total_income").values + household_weight = sim.calculate("household_weight").values first_half = ( np.concatenate( [ @@ -45,7 +49,7 @@ def impute_capital_gains(dataset, time_period: int): > 0 ) # Give capital gains to one adult aged 15+ in each household - adult_index = sim.calculate("adult_index", time_period) + adult_index = sim.calculate("adult_index").values == 1 in_person_second_half = np.zeros(len(ti)) > 0 in_person_second_half[len(ti) // 2 :] = True has_cg = np.zeros(len(ti)) > 0 @@ -88,13 +92,16 @@ def loss(blend_factor): pred_cg_in_income_range = ( blended_household_weight * household_cg_in_income_range_count ).sum() - pred_pct_with_gains = pred_cg_in_income_range / pred_ti_in_range + pred_pct_with_gains = pred_cg_in_income_range / torch.clip( + pred_ti_in_range, 1 + ) loss += (pred_pct_with_gains - true_pct_with_gains) ** 2 return loss optimiser = Adam([blend_factor], lr=1e-1) progress = range(100) + logging.info("Splitting household weights into has-gains and no-gains") for i in progress: optimiser.zero_grad() loss_value = loss(blend_factor) @@ -116,6 +123,8 @@ def loss(blend_factor): # Impute actual capital gains amounts given gains new_cg = np.zeros(len(ti)) + logging.info("Imputing capital gains among those with gains") + for i in range(len(capital_gains)): row = capital_gains.iloc[i] spline = UnivariateSpline( @@ -127,57 +136,25 @@ def loss(blend_factor): upper = row.maximum_total_income ti_in_range = (ti >= lower) * (ti < upper) in_target_range = has_cg * ti_in_range > 0 - quantiles = np.random.random(int(in_target_range.values.sum())) + quantiles = np.random.random(int(in_target_range.sum())) pred_capital_gains = spline(quantiles) new_cg[in_target_range] = pred_capital_gains return new_cg, new_household_weight -def stack_datasets(data_1, data_2): - assert isinstance( - data_1[list(data_1.keys())[0]], dict - ), "Data must be in variable-time-period format." - joined_data = {} - - for variable in data_1: - joined_data[variable] = {} - for time_period in data_1[variable]: - if "_id" in variable: - joined_data[variable][time_period] = np.concatenate( - [ - data_1[variable][time_period], - data_2[variable][time_period] - + data_1[variable][time_period].max(), - ] - ) - else: - joined_data[variable][time_period] = np.concatenate( - [ - data_1[variable][time_period], - data_2[variable][time_period], - ] - ) - - return joined_data - - -def impute_cg_to_dataset(dataset: Dataset): - data = dataset.load_dataset() - zero_weight_copy_1 = copy.deepcopy(data) - zero_weight_copy_2 = copy.deepcopy(data) - - for time_period in zero_weight_copy_2["household_weight"]: - zero_weight_copy_2["household_weight"][time_period] = np.zeros_like( - zero_weight_copy_1["household_weight"][time_period] - ) - - data = stack_datasets(data, zero_weight_copy_2) +def impute_capital_gains(dataset: UKSingleYearDataset) -> UKSingleYearDataset: + zero_weight_copy = dataset.copy() + zero_weight_copy.household.household_weight = 1 + data = stack_datasets( + dataset, + zero_weight_copy, + ) - dataset.save_dataset(data) + pred_cg, household_weight = impute_cg_to_doubled_dataset(data) - pred_cg, household_weights_22 = impute_capital_gains(dataset, 2022) + data.person["capital_gains"] = pred_cg + data.household["household_weight"] = household_weight - data["capital_gains"] = {2022: pred_cg} - data["household_weight"][2022] = household_weights_22 - dataset.save_dataset(data) + data.validate() + return data diff --git a/policyengine_uk_data/utils/imputations/consumption.py b/policyengine_uk_data/datasets/imputations/consumption.py similarity index 83% rename from policyengine_uk_data/utils/imputations/consumption.py rename to policyengine_uk_data/datasets/imputations/consumption.py index f5d6f3a5d..e42614c9f 100644 --- a/policyengine_uk_data/utils/imputations/consumption.py +++ b/policyengine_uk_data/datasets/imputations/consumption.py @@ -3,6 +3,9 @@ import numpy as np import yaml from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk import Microsimulation +from policyengine_uk_data.utils.stack import stack_datasets LCFS_TAB_FOLDER = STORAGE_FOLDER / "lcfs_2021_22" @@ -146,15 +149,34 @@ def save_imputation_models(): consumption.save( STORAGE_FOLDER / "consumption.pkl", ) + return consumption def create_consumption_model(overwrite_existing: bool = False): + from policyengine_uk_data.utils.qrf import QRF + if ( STORAGE_FOLDER / "consumption.pkl" ).exists() and not overwrite_existing: - return - save_imputation_models() + return QRF(file_path=STORAGE_FOLDER / "consumption.pkl") + return save_imputation_models() + + +def impute_consumption(dataset: UKSingleYearDataset) -> UKSingleYearDataset: + # Impute wealth, assuming same time period as trained data + dataset = dataset.copy() + + model = create_consumption_model() + sim = Microsimulation(dataset=dataset) + predictors = model.input_columns + + input_df = sim.calculate_dataframe(predictors, map_to="household") + + output_df = model.predict(input_df) + + for column in output_df.columns: + dataset.household[column] = output_df[column].values + dataset.validate() -if __name__ == "__main__": - create_consumption_model() + return dataset diff --git a/policyengine_uk_data/datasets/imputations/income.py b/policyengine_uk_data/datasets/imputations/income.py new file mode 100644 index 000000000..248363816 --- /dev/null +++ b/policyengine_uk_data/datasets/imputations/income.py @@ -0,0 +1,180 @@ +""" +Income imputation using Survey of Personal Incomes data. + +This module imputes detailed income components (employment, self-employment, +pensions, property, savings interest, dividends) using machine learning +models trained on HMRC Survey of Personal Incomes (SPI) data. +""" + +import pandas as pd +from pathlib import Path +import numpy as np +from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk import Microsimulation +from policyengine_uk_data.utils.stack import stack_datasets +from policyengine_uk_data.utils.subsample import subsample_dataset + +SPI_TAB_FOLDER = STORAGE_FOLDER / "spi_2020_21" +SPI_RENAMES = dict( + private_pension_income="PENSION", + self_employment_income="PROFITS", + property_income="INCPROP", + savings_interest_income="INCBBS", + dividend_income="DIVIDENDS", + blind_persons_allowance="BPADUE", + married_couples_allowance="MCAS", + gift_aid="GIFTAID", + capital_allowances="CAPALL", + deficiency_relief="DEFICIEN", + covenanted_payments="COVNTS", + charitable_investment_gifts="GIFTINV", + employment_expenses="EPB", + other_deductions="MOTHDED", + person_weight="FACT", + benunit_weight="FACT", + household_weight="FACT", + state_pension="SRP", +) + + +def generate_spi_table(spi: pd.DataFrame): + """ + Clean and transform SPI data for income imputation model training. + + Args: + spi: Raw SPI survey data DataFrame. + + Returns: + Cleaned DataFrame with age and region mappings applied. + """ + LOWER = np.array([0, 16, 25, 35, 45, 55, 65, 75]) + UPPER = np.array([16, 25, 35, 45, 55, 65, 75, 80]) + age_range = spi.AGERANGE + spi["age"] = LOWER[age_range] + np.random.rand(len(spi)) * ( + UPPER[age_range] - LOWER[age_range] + ) + + REGIONS = { + 1: "NORTH_EAST", + 2: "NORTH_WEST", + 3: "YORKSHIRE", + 4: "EAST_MIDLANDS", + 5: "WEST_MIDLANDS", + 6: "EAST_OF_ENGLAND", + 7: "LONDON", + 8: "SOUTH_EAST", + 9: "SOUTH_WEST", + 10: "WALES", + 11: "SCOTLAND", + 12: "NORTHERN_IRELAND", + } + + spi["region"] = np.array([REGIONS.get(x, "LONDON") for x in spi.GORCODE]) + + spi["gender"] = np.where(spi.SEX == 1, "MALE", "FEMALE") + + for rename in SPI_RENAMES: + spi[rename] = spi[SPI_RENAMES[rename]] + + spi["employment_income"] = spi[["PAY", "EPB", "TAXTERM"]].sum(axis=1) + + spi = pd.concat( + [ + spi.sample(20_000), + spi[spi.TI > 1_000_000], + ] + ) + + return spi + + +PREDICTORS = [ + "age", + "gender", + "region", +] + +IMPUTATIONS = [ + "employment_income", + "self_employment_income", + "savings_interest_income", + "dividend_income", + "private_pension_income", + "property_income", +] + + +def save_imputation_models(): + """ + Train and save income imputation model. + + Returns: + Trained QRF model for income imputation. + """ + from policyengine_uk_data.utils import QRF + + income = QRF() + spi = pd.read_csv(SPI_TAB_FOLDER / "put2021uk.tab", delimiter="\t") + spi = generate_spi_table(spi) + spi = spi[PREDICTORS + IMPUTATIONS] + income.fit(spi[PREDICTORS], spi[IMPUTATIONS]) + income.save(STORAGE_FOLDER / "income.pkl") + return income + + +def create_income_model(overwrite_existing: bool = False): + """ + Create or load income imputation model. + + Args: + overwrite_existing: Whether to retrain model if it exists. + + Returns: + QRF model for income imputation. + """ + from policyengine_uk_data.utils.qrf import QRF + + if (STORAGE_FOLDER / "income.pkl").exists() and not overwrite_existing: + return QRF(file_path=STORAGE_FOLDER / "income.pkl") + return save_imputation_models() + + +def impute_income(dataset: UKSingleYearDataset) -> UKSingleYearDataset: + """ + Impute detailed income components using trained model. + + Uses SPI-trained models to predict various income sources for individuals + based on age, gender, and region. Creates a synthetic population with + the imputed income data. + + Args: + dataset: PolicyEngine UK dataset to augment with income data. + + Returns: + Combined dataset with original data plus synthetic high-income individuals. + """ + # Impute wealth, assuming same time period as trained data + dataset = dataset.copy() + zero_weight_copy = dataset.copy() + zero_weight_copy.household.household_weight = 0 + zero_weight_copy = subsample_dataset(zero_weight_copy, 10_000) + + model = create_income_model() + sim = Microsimulation(dataset=zero_weight_copy) + + input_df = sim.calculate_dataframe(["age", "gender", "region"]) + + output_df = model.predict(input_df) + + for column in output_df.columns: + zero_weight_copy.person[column] = output_df[column].fillna(0).values + + zero_weight_copy.validate() + + data = stack_datasets( + dataset, + zero_weight_copy, + ) + + return data diff --git a/policyengine_uk_data/datasets/imputations/services/__init__.py b/policyengine_uk_data/datasets/imputations/services/__init__.py new file mode 100644 index 000000000..c90ce567d --- /dev/null +++ b/policyengine_uk_data/datasets/imputations/services/__init__.py @@ -0,0 +1,3 @@ +"""Public service imputation module for UK households.""" + +from .services import impute_services diff --git a/policyengine_uk_data/datasets/imputations/services/etb.py b/policyengine_uk_data/datasets/imputations/services/etb.py new file mode 100644 index 000000000..cc9e28fda --- /dev/null +++ b/policyengine_uk_data/datasets/imputations/services/etb.py @@ -0,0 +1,175 @@ +""" +Imputation model for public services received by households. + +This module creates a quantile regression forest model to predict the value of +public services received by households based on demographic characteristics. +""" + +import pandas as pd +import numpy as np +from pathlib import Path +import logging +from policyengine_uk import Microsimulation +from huggingface_hub import hf_hub_download +import os +from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk_data.utils.qrf import QRF +from policyengine_uk.data import UKSingleYearDataset + +# Constants +WEEKS_IN_YEAR = 52 + +# Variables used to predict public service receipt +PREDICTORS = [ + "is_adult", + "is_child", + "is_SP_age", + "count_primary_education", + "count_secondary_education", + "count_further_education", + "dla", + "pip", + "hbai_household_net_income", +] + +# Public service variables to impute +OUTPUTS = [ + "dfe_education_spending", + "rail_subsidy_spending", + "bus_subsidy_spending", +] + + +def create_public_services_model(overwrite_existing: bool = False) -> None: + """ + Create and save a model for imputing public service receipt values. + + Args: + overwrite_existing: Whether to overwrite an existing model file. + """ + # Check if model already exists and we're not overwriting + if ( + STORAGE_FOLDER / "public_services.pkl" + ).exists() and not overwrite_existing: + return + + etb_path = STORAGE_FOLDER / "etb_1977_21" / "householdv2_1977-2021.tab" + + # Load Effects of Taxes and Benefits (ETB) dataset + etb = pd.read_csv(etb_path, delimiter="\t") + etb = etb[etb.year == etb.year.max()] # Use most recent year + etb = etb.replace(" ", np.nan) + + # Select relevant columns + etb = etb[ + [ + "adults", + "childs", + "disinc", + "benk", + "educ", + "totnhs", + "rail", + "bussub", + "hsub", + "hhold_adj_weight", + "noretd", + "primed", + "secoed", + "wagern", + "welf", + "furted", + "disliv", + "pips", + ] + ] + etb = etb.dropna().astype(float) + + # Prepare training data + train = pd.DataFrame() + train["is_adult"] = etb.adults + train["is_child"] = etb.childs + train["hbai_household_net_income"] = etb.disinc * WEEKS_IN_YEAR + train["is_SP_age"] = etb.noretd + train["count_primary_education"] = etb.primed + train["count_secondary_education"] = etb.secoed + train["count_further_education"] = etb.furted + train["dla"] = etb.disliv + train["pip"] = etb.pips + + # Output variables (annualized) + train["dfe_education_spending"] = etb.educ * WEEKS_IN_YEAR + train["rail_subsidy_spending"] = etb.rail * WEEKS_IN_YEAR + train["bus_subsidy_spending"] = etb.bussub * WEEKS_IN_YEAR + + # Train model + model = QRF() + model.fit(X=train[PREDICTORS], y=train[OUTPUTS]) + + return model + + +def impute_public_services(efrs: pd.DataFrame) -> pd.DataFrame: + """ + Impute public services received by households. + + Args: + efrs: DataFrame containing household data. + + Returns: + DataFrame with imputed public service values. + """ + # Create model if it doesn't exist + model = create_public_services_model() + + efrs_h = efrs.groupby("household_id").sum() + household_count_person = efrs_h.is_adult.values + efrs_h.is_child.values + + # Impute public services + efrs_h[OUTPUTS] = model.predict(efrs_h[PREDICTORS]).values + + for output in OUTPUTS: + efrs_h[output] /= household_count_person + efrs[output] = efrs_h[output].loc[efrs["household_id"].values].values + + return efrs + + +def create_efrs_input_dataset(dataset: UKSingleYearDataset) -> pd.DataFrame: + sim = Microsimulation( + dataset=dataset, + ) + + variables = [ + "age", + "gender", + "household_weight", + "region", + "household_id", + "is_adult", + "is_child", + "is_SP_age", + "dla", + "pip", + "household_count_people", + "hbai_household_net_income", + "equiv_hbai_household_net_income", + ] + education = sim.calculate("current_education") + + df = sim.calculate_dataframe(variables) + + df["count_primary_education"] = education == "PRIMARY" + df["count_secondary_education"] = education == "LOWER_SECONDARY" + df["count_further_education"] = education.isin( + ["UPPER_SECONDARY", "TERTIARY"] + ) + df["hbai_household_net_income"] = ( + df["hbai_household_net_income"] / df["household_count_people"] + ) + df["equiv_hbai_household_net_income"] = ( + df["equiv_hbai_household_net_income"] / df["household_count_people"] + ) + + data = pd.DataFrame(df) + return data diff --git a/policyengine_uk_data/datasets/imputations/services/nhs.py b/policyengine_uk_data/datasets/imputations/services/nhs.py new file mode 100644 index 000000000..fa25d7ca5 --- /dev/null +++ b/policyengine_uk_data/datasets/imputations/services/nhs.py @@ -0,0 +1,144 @@ +""" +NHS usage imputation for UK households. + +This module imputes NHS service usage (A&E, admitted patients, outpatients) +and associated spending based on age and gender demographics. +""" + +import pandas as pd +import numpy as np +import logging +from policyengine_uk_data.storage import STORAGE_FOLDER + + +def create_nhs_usage_data(efrs: pd.DataFrame): + """ + Create NHS usage data by age and gender demographics. + + Processes NHS consumption data by age group and gender, calculating + per-person averages for service usage and spending. + + Args: + efrs: DataFrame containing person-level data with age, gender, and weights. + + Returns: + DataFrame with per-person NHS usage and spending by demographic groups. + """ + # First, read the data + + nhs = pd.read_csv(STORAGE_FOLDER / "nhs_consumption_by_age_gender.csv") + + # Clean age bounds + + def get_age_bounds(age_group: str): + """Extract lower and upper age bounds from age group string.""" + if age_group == "0 years": + return 0, 1 + if age_group == "95 years or older": + return 95, 120 + + if "-" in age_group: + lower, upper = age_group[:5].split("-") + lower = int(lower.strip()) + upper = int(upper.strip()) + return lower, upper + 1 + + nhs["Lower age"] = nhs["Age group"].apply(lambda x: get_age_bounds(x)[0]) + nhs["Upper age"] = nhs["Age group"].apply(lambda x: get_age_bounds(x)[1]) + + nhs = nhs.drop(columns=["Age group"]) + + index_cols = ["Lower age", "Upper age", "Gender", "Service"] + + # Get counts and total costs as columns + nhs = nhs.pivot( + index=index_cols, + columns="Metric", + values="Total", + ) + + # Roll 80+ into 80-85 + + nhs = nhs.reset_index() + + over_80_values = ( + nhs[nhs["Lower age"] == 80].set_index(["Gender", "Service"]) + + nhs[nhs["Lower age"] > 80].groupby(["Gender", "Service"]).sum() + ).reset_index() + + nhs[nhs["Lower age"] == 80][["Activity Count", "Total Cost"]] = ( + over_80_values[["Activity Count", "Total Cost"]] + ) + nhs = nhs[nhs["Lower age"] <= 80] + nhs[nhs["Lower age"] == 80]["Upper age"] = 120 + + nhs["Spending per unit"] = nhs["Total Cost"] / nhs["Activity Count"] + + # Now add total number in demographic groups using PE + + nhs["Total people"] = np.ones_like(nhs["Total Cost"]) + + for i in range(len(nhs)): + row = nhs.iloc[i] + count = efrs[efrs.age.between(row["Lower age"], row["Upper age"] - 1)][ + efrs.gender == row.Gender.upper() + ].household_weight.values.sum() + nhs.loc[i, "Total people"] = count + + nhs["Per-person average units"] = ( + nhs["Activity Count"] / nhs["Total people"] + ) + nhs["Per-person average spending"] = ( + nhs["Total Cost"] / nhs["Total people"] + ) + indirect_cost_adjustment_factor = ( + 202e9 / nhs["Total Cost"].sum() + ) # £202 billion 2025/26 budget + + nhs["Per-person average spending"] *= indirect_cost_adjustment_factor + + return nhs.pivot( + index=["Lower age", "Upper age", "Gender"], + columns="Service", + values=["Per-person average units", "Per-person average spending"], + ).reset_index() + + +def impute_nhs_usage(efrs: pd.DataFrame): + """ + Impute NHS service usage and spending for individuals. + + Assigns NHS visit counts and spending amounts to individuals based on + their age and gender using demographic-specific averages. + + Args: + efrs: DataFrame containing individual data with age and gender. + + Returns: + DataFrame with added NHS usage and spending variables. + """ + nhs_usage = create_nhs_usage_data(efrs) + visit_variables = [ + "a_and_e_visits", + "admitted_patient_visits", + "outpatient_visits", + ] + spending_variables = [ + "nhs_a_and_e_spending", + "nhs_admitted_patient_spending", + "nhs_outpatient_spending", + ] + + variables = visit_variables + spending_variables + + for i, row in nhs_usage.iterrows(): + selection = efrs.age.between(row.values[0], row.values[1]) & ( + efrs.gender == row.values[2].upper() + ) + for j, service in enumerate(row.values[3:]): + efrs.loc[selection, variables[j]] = service + + efrs["nhs_visits"] = efrs[visit_variables].sum(axis=1) + efrs["nhs_spending"] = efrs[spending_variables].sum(axis=1) + + return efrs diff --git a/policyengine_uk_data/datasets/imputations/services/services.py b/policyengine_uk_data/datasets/imputations/services/services.py new file mode 100644 index 000000000..6d76525eb --- /dev/null +++ b/policyengine_uk_data/datasets/imputations/services/services.py @@ -0,0 +1,63 @@ +""" +Service imputations for UK households. + +This module coordinates the imputation of various public services including +NHS usage, education spending, and transport subsidies to UK households. +""" + +from policyengine_uk.data import UKSingleYearDataset +from .nhs import impute_nhs_usage +from .etb import impute_public_services, create_efrs_input_dataset + + +def impute_services( + dataset: UKSingleYearDataset, +) -> UKSingleYearDataset: + """ + Impute public service usage and spending for households. + + This function combines NHS usage imputations with other public services + (education, rail, and bus subsidies) to create a comprehensive dataset + of household public service consumption. + + Args: + dataset: A PolicyEngine UK dataset containing household and person data. + + Returns: + Updated dataset with imputed service usage and spending variables. + """ + dataset = dataset.copy() + input_data = create_efrs_input_dataset(dataset) + + input_data = impute_nhs_usage(input_data) + input_data = impute_public_services(input_data) + + for household_imputations in [ + "dfe_education_spending", + "rail_subsidy_spending", + "bus_subsidy_spending", + ]: + dataset.household[household_imputations] = ( + input_data[household_imputations] + .groupby(input_data.household_id) + .sum() + .values + ) + + visit_variables = [ + "a_and_e_visits", + "admitted_patient_visits", + "outpatient_visits", + ] + spending_variables = [ + "nhs_a_and_e_spending", + "nhs_admitted_patient_spending", + "nhs_outpatient_spending", + ] + + for person_imputations in visit_variables + spending_variables: + dataset.person[person_imputations] = input_data[ + person_imputations + ].values + + return dataset diff --git a/policyengine_uk_data/datasets/imputations/vat.py b/policyengine_uk_data/datasets/imputations/vat.py new file mode 100644 index 000000000..1071f2427 --- /dev/null +++ b/policyengine_uk_data/datasets/imputations/vat.py @@ -0,0 +1,117 @@ +""" +VAT expenditure imputation using Effects of Taxes and Benefits data. + +This module imputes household VAT expenditure rates based on demographic +characteristics using machine learning models trained on ETB survey data. +""" + +import pandas as pd +from pathlib import Path +import numpy as np +from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk import Microsimulation + +ETB_TAB_FOLDER = STORAGE_FOLDER / "etb_1977_21" + +CONSUMPTION_PCT_REDUCED_RATE = 0.03 # From OBR's VAT page +CURRENT_VAT_RATE = 0.2 + +PREDICTORS = ["is_adult", "is_child", "is_SP_age", "household_net_income"] +IMPUTATIONS = ["full_rate_vat_expenditure_rate"] + + +def generate_etb_table(etb: pd.DataFrame): + """ + Clean and transform ETB data for VAT imputation model training. + + Args: + etb: Raw ETB survey data DataFrame. + + Returns: + Cleaned DataFrame with VAT expenditure rates calculated. + """ + etb_2020 = etb[etb.year == 2020].dropna() + for col in etb_2020: + etb_2020[col] = pd.to_numeric(etb_2020[col], errors="coerce") + + etb_2020_df = pd.DataFrame() + etb_2020_df["is_adult"] = etb_2020.adults + etb_2020_df["is_child"] = etb_2020.childs + etb_2020_df["is_SP_age"] = etb_2020.noretd + etb_2020_df["household_net_income"] = etb_2020.disinc * 52 + etb_2020_df["full_rate_vat_expenditure_rate"] = ( + etb_2020.totvat * (1 - CONSUMPTION_PCT_REDUCED_RATE) / CURRENT_VAT_RATE + ) / (etb_2020.expdis - etb_2020.totvat) + return etb_2020_df[~etb_2020_df.full_rate_vat_expenditure_rate.isna()] + + +def save_imputation_models(): + """ + Train and save VAT imputation model. + + Returns: + Trained QRF model for VAT imputation. + """ + from policyengine_uk_data.utils.qrf import QRF + + vat = QRF() + etb = pd.read_csv( + ETB_TAB_FOLDER / "householdv2_1977-2021.tab", + delimiter="\t", + low_memory=False, + ) + etb = generate_etb_table(etb) + etb = etb[PREDICTORS + IMPUTATIONS] + vat.fit(etb[PREDICTORS], etb[IMPUTATIONS]) + vat.save(STORAGE_FOLDER / "vat.pkl") + return vat + + +def create_vat_model(overwrite_existing: bool = False): + """ + Create or load VAT imputation model. + + Args: + overwrite_existing: Whether to retrain model if it exists. + + Returns: + QRF model for VAT expenditure imputation. + """ + from policyengine_uk_data.utils.qrf import QRF + + if (STORAGE_FOLDER / "vat.pkl").exists() and not overwrite_existing: + return QRF(file_path=STORAGE_FOLDER / "vat.pkl") + return save_imputation_models() + + +def impute_vat(dataset: UKSingleYearDataset) -> UKSingleYearDataset: + """ + Impute household VAT expenditure rates using trained model. + + Uses ETB-trained models to predict VAT expenditure rates for households + based on demographic composition and income. + + Args: + dataset: PolicyEngine UK dataset to augment with VAT data. + + Returns: + Dataset with imputed VAT expenditure variables added to household table. + """ + # Impute wealth, assuming same time period as trained data + dataset = dataset.copy() + + model = create_vat_model() + sim = Microsimulation(dataset=dataset) + predictors = model.input_columns + + input_df = sim.calculate_dataframe(predictors, map_to="household") + + output_df = model.predict(input_df) + + for column in output_df.columns: + dataset.household[column] = output_df[column].values + + dataset.validate() + + return dataset diff --git a/policyengine_uk_data/utils/imputations/wealth.py b/policyengine_uk_data/datasets/imputations/wealth.py similarity index 67% rename from policyengine_uk_data/utils/imputations/wealth.py rename to policyengine_uk_data/datasets/imputations/wealth.py index c03ab8819..3dabc9596 100644 --- a/policyengine_uk_data/utils/imputations/wealth.py +++ b/policyengine_uk_data/datasets/imputations/wealth.py @@ -1,5 +1,16 @@ +""" +Household wealth imputation using Wealth and Assets Survey data. + +This module imputes various types of household wealth (property, financial, +corporate) using machine learning models trained on the UK Wealth and Assets +Survey (WAS) data. +""" + import pandas as pd from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk import Microsimulation + WAS_TAB_FOLDER = STORAGE_FOLDER / "was_2006_20" @@ -45,6 +56,15 @@ def generate_was_table(was: pd.DataFrame): + """ + Clean and transform WAS data for model training. + + Args: + was: Raw WAS survey data DataFrame. + + Returns: + Cleaned DataFrame with renamed columns and computed variables. + """ was = was.rename(columns={col: col.lower() for col in was.columns}) to_remove = [] @@ -129,6 +149,12 @@ def generate_was_table(was: pd.DataFrame): def save_imputation_models(): + """ + Train and save wealth imputation model. + + Returns: + Trained QRF model. + """ from policyengine_uk_data.utils.qrf import QRF was = pd.read_csv( @@ -145,13 +171,56 @@ def save_imputation_models(): was[IMPUTE_VARIABLES], ) wealth.save(STORAGE_FOLDER / "wealth.pkl") + return wealth def create_wealth_model(overwrite_existing: bool = False): + """ + Create or load wealth imputation model. + + Args: + overwrite_existing: Whether to retrain model if it exists. + + Returns: + QRF model for wealth imputation. + """ + from policyengine_uk_data.utils.qrf import QRF + if (STORAGE_FOLDER / "wealth.pkl").exists() and not overwrite_existing: - return - save_imputation_models() + return QRF(file_path=STORAGE_FOLDER / "wealth.pkl") + return save_imputation_models() + + +def impute_wealth(dataset: UKSingleYearDataset) -> UKSingleYearDataset: + """ + Impute household wealth variables using trained model. + + Uses WAS-trained models to predict various wealth components for + households based on income, demographics, and housing characteristics. + + Args: + dataset: PolicyEngine UK dataset to augment with wealth data. + + Returns: + Dataset with imputed wealth variables added to household table. + """ + # Impute wealth, assuming same time period as trained data + dataset = dataset.copy() + + model = create_wealth_model() + sim = Microsimulation(dataset=dataset) + predictors = model.input_columns + + input_df = sim.calculate_dataframe(predictors, map_to="household") + + input_df["region"] = input_df["region"].replace( + "NORTHERN_IRELAND", "WALES" + ) # WAS doesn't sample NI -> put NI households in Wales (closest aggregate) + output_df = model.predict(input_df) + + for column in output_df.columns: + dataset.household[column] = output_df[column].values + dataset.validate() -if __name__ == "__main__": - create_wealth_model() + return dataset diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/boundary_changes/__init__.py b/policyengine_uk_data/datasets/local_areas/constituencies/boundary_changes/__init__.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/boundary_changes/__init__.py rename to policyengine_uk_data/datasets/local_areas/constituencies/boundary_changes/__init__.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/boundary_changes/boundary_changes.csv b/policyengine_uk_data/datasets/local_areas/constituencies/boundary_changes/boundary_changes.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/boundary_changes/boundary_changes.csv rename to policyengine_uk_data/datasets/local_areas/constituencies/boundary_changes/boundary_changes.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/boundary_changes/mapping_matrix.py b/policyengine_uk_data/datasets/local_areas/constituencies/boundary_changes/mapping_matrix.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/boundary_changes/mapping_matrix.py rename to policyengine_uk_data/datasets/local_areas/constituencies/boundary_changes/mapping_matrix.py diff --git a/policyengine_uk_data/datasets/local_areas/constituencies/calibrate.py b/policyengine_uk_data/datasets/local_areas/constituencies/calibrate.py new file mode 100644 index 000000000..0b0f2d57b --- /dev/null +++ b/policyengine_uk_data/datasets/local_areas/constituencies/calibrate.py @@ -0,0 +1,116 @@ +import pandas as pd +from policyengine_uk_data.utils.calibrate import calibrate_local_areas +from policyengine_uk_data.datasets.local_areas.constituencies.loss import ( + create_constituency_target_matrix, + create_national_target_matrix, +) +from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset + + +def calibrate( + dataset: UKSingleYearDataset, + excluded_training_targets=[], + log_csv="calibration_log.csv", + verbose: bool = False, +): + return calibrate_local_areas( + dataset=dataset, + matrix_fn=create_constituency_target_matrix, + national_matrix_fn=create_national_target_matrix, + area_count=650, + weight_file="parliamentary_constituency_weights.h5", + excluded_training_targets=excluded_training_targets, + log_csv=log_csv, + verbose=verbose, + area_name="Constituency", + get_performance=get_performance, + ) + + +def get_performance(weights, m_c, y_c, m_n, y_n, excluded_targets): + constituency_target_matrix, constituency_actuals = m_c, y_c + national_target_matrix, national_actuals = m_n, y_n + constituencies = pd.read_csv(STORAGE_FOLDER / "constituencies_2024.csv") + constituency_wide = weights @ constituency_target_matrix + constituency_wide.index = constituencies.code.values + constituency_wide["name"] = constituencies.name.values + + constituency_results = pd.melt( + constituency_wide.reset_index(), + id_vars=["index", "name"], + var_name="variable", + value_name="value", + ) + + constituency_actuals.index = constituencies.code.values + constituency_actuals["name"] = constituencies.name.values + constituency_actuals_long = pd.melt( + constituency_actuals.reset_index(), + id_vars=["index", "name"], + var_name="variable", + value_name="value", + ) + + constituency_target_validation = pd.merge( + constituency_results, + constituency_actuals_long, + on=["index", "variable"], + suffixes=("_target", "_actual"), + ) + constituency_target_validation.drop("name_actual", axis=1, inplace=True) + constituency_target_validation.columns = [ + "index", + "name", + "metric", + "estimate", + "target", + ] + + constituency_target_validation["error"] = ( + constituency_target_validation["estimate"] + - constituency_target_validation["target"] + ) + constituency_target_validation["abs_error"] = ( + constituency_target_validation["error"].abs() + ) + constituency_target_validation["rel_abs_error"] = ( + constituency_target_validation["abs_error"] + / constituency_target_validation["target"] + ) + + national_performance = weights.sum(axis=0) @ national_target_matrix + national_target_validation = pd.DataFrame( + { + "metric": national_performance.index, + "estimate": national_performance.values, + } + ) + national_target_validation["target"] = national_actuals.values + + national_target_validation["error"] = ( + national_target_validation["estimate"] + - national_target_validation["target"] + ) + national_target_validation["abs_error"] = national_target_validation[ + "error" + ].abs() + national_target_validation["rel_abs_error"] = ( + national_target_validation["abs_error"] + / national_target_validation["target"] + ) + + df = pd.concat( + [ + constituency_target_validation, + national_target_validation.assign(name="UK", index=0), + ] + ).reset_index(drop=True) + + df["validation"] = df.metric.isin(excluded_targets) + + return df + + +if __name__ == "__main__": + calibrate() diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/loss.py b/policyengine_uk_data/datasets/local_areas/constituencies/loss.py similarity index 77% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/loss.py rename to policyengine_uk_data/datasets/local_areas/constituencies/loss.py index bf9c6cb72..18bdeef40 100644 --- a/policyengine_uk_data/datasets/frs/local_areas/constituencies/loss.py +++ b/policyengine_uk_data/datasets/local_areas/constituencies/loss.py @@ -12,30 +12,37 @@ create_target_matrix as create_national_target_matrix, ) from policyengine_uk_data.storage import STORAGE_FOLDER -from policyengine_uk_data.datasets.frs.local_areas.constituencies.boundary_changes.mapping_matrix import ( +from policyengine_uk_data.datasets.local_areas.constituencies.boundary_changes.mapping_matrix import ( mapping_matrix, ) +from policyengine_uk.data import UKSingleYearDataset FOLDER = Path(__file__).parent def create_constituency_target_matrix( - dataset: str = "enhanced_frs_2022_23", - time_period: int = 2025, + dataset: UKSingleYearDataset, + time_period: int = None, reform=None, uprate: bool = True, ): + if time_period is None: + time_period = dataset.time_period ages = pd.read_csv(FOLDER / "targets" / "age.csv") + national_demographics = pd.read_csv(STORAGE_FOLDER / "demographics.csv") incomes = pd.read_csv(FOLDER / "targets" / "spi_by_constituency.csv") employment_incomes = pd.read_csv( FOLDER / "targets" / "employment_income.csv" ) sim = Microsimulation(dataset=dataset, reform=reform) - sim.default_calculation_period = time_period + sim.default_calculation_period = dataset.time_period national_incomes = pd.read_csv(STORAGE_FOLDER / "incomes_projection.csv") - national_incomes = national_incomes[national_incomes.year == 2025] + national_incomes = national_incomes[ + national_incomes.year + == max(national_incomes.year.min(), int(dataset.time_period)) + ] matrix = pd.DataFrame() y = pd.DataFrame() @@ -77,7 +84,15 @@ def create_constituency_target_matrix( * national_consistency_adjustment_factor ) + uk_total_population = ( + national_demographics[national_demographics.name == "uk_population"][ + str(time_period) + ].values[0] + * 1e6 + ) + age = sim.calculate("age").values + targets_total_pop = 0 for lower_age in range(0, 80, 10): upper_age = lower_age + 10 @@ -94,6 +109,16 @@ def create_constituency_target_matrix( age_str = f"{lower_age}_{upper_age}" y[f"age/{age_str}"] = age_count.values + targets_total_pop += age_count.values.sum() + + # Adjust for consistency + for lower_age in range(0, 80, 10): + upper_age = lower_age + 10 + + in_age_band = (age >= lower_age) & (age < upper_age) + + age_str = f"{lower_age}_{upper_age}" + y[f"age/{age_str}"] *= uk_total_population / targets_total_pop * 0.9 employment_income = sim.calculate("employment_income").values bounds = list( @@ -101,11 +126,12 @@ def create_constituency_target_matrix( ) + [np.inf] for lower_bound, upper_bound in zip(bounds[:-1], bounds[1:]): + continue if ( - lower_bound <= 15_000 + lower_bound <= 20_000 ): # Skip some targets with very small sample sizes continue - if upper_bound >= 200_000: + if upper_bound >= 100_000: continue national_data_row = national_incomes[ @@ -152,9 +178,6 @@ def create_constituency_target_matrix( amount_target * adjustment ) - if uprate: - y = uprate_targets(y, time_period) - const_2024 = pd.read_csv(STORAGE_FOLDER / "constituencies_2024.csv") const_2010 = pd.read_csv(STORAGE_FOLDER / "constituencies_2010.csv") @@ -197,44 +220,3 @@ def create_country_mask( r[i] = household_countries == constituency_countries[i] return r - - -def uprate_targets(y: pd.DataFrame, target_year: int = 2025) -> pd.DataFrame: - # Uprate age targets from 2020, taxable income targets from 2021, employment income targets from 2023. - # Use PolicyEngine uprating factors. - from policyengine_uk_data.datasets.frs.frs import FRS_2020_21 - - sim = Microsimulation(dataset=FRS_2020_21) - matrix_20, y_20, _ = create_constituency_target_matrix( - FRS_2020_21, 2020, uprate=False - ) - matrix_21, y_21, _ = create_constituency_target_matrix( - FRS_2020_21, 2021, uprate=False - ) - matrix_23, y_23, _ = create_constituency_target_matrix( - FRS_2020_21, 2023, uprate=False - ) - matrix_final, y_final, _ = create_constituency_target_matrix( - FRS_2020_21, target_year, uprate=False - ) - - weights_20 = sim.calculate("household_weight", 2020) - weights_21 = sim.calculate("household_weight", 2021) - weights_23 = sim.calculate("household_weight", 2023) - weights_final = sim.calculate("household_weight", target_year) - - rel_change_20_final = (weights_final @ matrix_final) / ( - weights_20 @ matrix_20 - ) - 1 - is_uprated_from_2020 = [ - col.startswith("age/") for col in matrix_20.columns - ] - uprating_from_2020 = np.zeros_like(matrix_20.columns, dtype=float) - uprating_from_2020[is_uprated_from_2020] = rel_change_20_final[ - is_uprated_from_2020 - ] - - uprating = uprating_from_2020 - y = y * (1 + uprating) - - return y diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/README.md b/policyengine_uk_data/datasets/local_areas/constituencies/targets/README.md similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/README.md rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/README.md diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/__init__.py b/policyengine_uk_data/datasets/local_areas/constituencies/targets/__init__.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/__init__.py rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/__init__.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/age.csv b/policyengine_uk_data/datasets/local_areas/constituencies/targets/age.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/age.csv rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/age.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/create_employment_incomes.py b/policyengine_uk_data/datasets/local_areas/constituencies/targets/create_employment_incomes.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/create_employment_incomes.py rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/create_employment_incomes.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/create_total_incomes.py b/policyengine_uk_data/datasets/local_areas/constituencies/targets/create_total_incomes.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/create_total_incomes.py rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/create_total_incomes.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/employment_income.csv b/policyengine_uk_data/datasets/local_areas/constituencies/targets/employment_income.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/employment_income.csv rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/employment_income.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/fill_missing_age_demographics.py b/policyengine_uk_data/datasets/local_areas/constituencies/targets/fill_missing_age_demographics.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/fill_missing_age_demographics.py rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/fill_missing_age_demographics.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/nomis_earning_jobs_data.xlsx b/policyengine_uk_data/datasets/local_areas/constituencies/targets/nomis_earning_jobs_data.xlsx similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/nomis_earning_jobs_data.xlsx rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/nomis_earning_jobs_data.xlsx diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/spi_by_constituency.csv b/policyengine_uk_data/datasets/local_areas/constituencies/targets/spi_by_constituency.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/spi_by_constituency.csv rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/spi_by_constituency.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/spi_constituency_raw.csv b/policyengine_uk_data/datasets/local_areas/constituencies/targets/spi_constituency_raw.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/spi_constituency_raw.csv rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/spi_constituency_raw.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/total_income.csv b/policyengine_uk_data/datasets/local_areas/constituencies/targets/total_income.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/constituencies/targets/total_income.csv rename to policyengine_uk_data/datasets/local_areas/constituencies/targets/total_income.csv diff --git a/policyengine_uk_data/datasets/local_areas/local_authorities/calibrate.py b/policyengine_uk_data/datasets/local_areas/local_authorities/calibrate.py new file mode 100644 index 000000000..f9f544e9b --- /dev/null +++ b/policyengine_uk_data/datasets/local_areas/local_authorities/calibrate.py @@ -0,0 +1,31 @@ +from policyengine_uk_data.utils.calibrate import calibrate_local_areas +from policyengine_uk_data.datasets.local_areas.local_authorities.loss import ( + create_local_authority_target_matrix, + create_national_target_matrix, +) +from policyengine_uk.data import UKSingleYearDataset + + +def calibrate( + dataset: UKSingleYearDataset, + verbose: bool = False, +): + return calibrate_local_areas( + dataset=dataset, + matrix_fn=lambda ds: create_local_authority_target_matrix( + ds, ds.time_period + ), + national_matrix_fn=lambda ds: create_national_target_matrix( + ds, ds.time_period + ), + area_count=360, + weight_file="local_authority_weights.h5", + excluded_training_targets=[], + log_csv=None, + verbose=verbose, + area_name="Local Authority", + ) + + +if __name__ == "__main__": + calibrate() diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/loss.py b/policyengine_uk_data/datasets/local_areas/local_authorities/loss.py similarity index 79% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/loss.py rename to policyengine_uk_data/datasets/local_areas/local_authorities/loss.py index 23358a301..57776a4b0 100644 --- a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/loss.py +++ b/policyengine_uk_data/datasets/local_areas/local_authorities/loss.py @@ -8,16 +8,19 @@ create_target_matrix as create_national_target_matrix, ) from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset FOLDER = Path(__file__).parent def create_local_authority_target_matrix( - dataset: str = "enhanced_frs_2022_23", - time_period: int = 2025, + dataset: UKSingleYearDataset, + time_period: int = None, reform=None, uprate: bool = True, ): + if time_period is None: + time_period = dataset.time_period ages = pd.read_csv(FOLDER / "targets" / "age.csv") incomes = pd.read_csv(FOLDER / "targets" / "spi_by_la.csv") employment_incomes = pd.read_csv( @@ -72,6 +75,16 @@ def create_local_authority_target_matrix( ) age = sim.calculate("age").values + national_demographics = pd.read_csv(STORAGE_FOLDER / "demographics.csv") + uk_total_population = ( + national_demographics[national_demographics.name == "uk_population"][ + str(time_period) + ].values[0] + * 1e6 + ) + + age = sim.calculate("age").values + targets_total_pop = 0 for lower_age in range(0, 80, 10): upper_age = lower_age + 10 @@ -88,6 +101,16 @@ def create_local_authority_target_matrix( age_str = f"{lower_age}_{upper_age}" y[f"age/{age_str}"] = age_count.values + targets_total_pop += age_count.values.sum() + + # Adjust for consistency + for lower_age in range(0, 80, 10): + upper_age = lower_age + 10 + + in_age_band = (age >= lower_age) & (age < upper_age) + + age_str = f"{lower_age}_{upper_age}" + y[f"age/{age_str}"] *= uk_total_population / targets_total_pop * 0.9 employment_income = sim.calculate("employment_income").values bounds = list( @@ -95,6 +118,7 @@ def create_local_authority_target_matrix( ) + [np.inf] for lower_bound, upper_bound in zip(bounds[:-1], bounds[1:]): + continue if ( lower_bound <= 15_000 ): # Skip some targets with very small sample sizes @@ -144,9 +168,6 @@ def create_local_authority_target_matrix( amount_target * adjustment ) - if uprate: - y = uprate_targets(y, time_period) - country_mask = create_country_mask( household_countries=sim.calculate("country").values, codes=la_codes.code, @@ -176,42 +197,3 @@ def create_country_mask( r[i] = household_countries == constituency_countries[i] return r - - -def uprate_targets(y: pd.DataFrame, target_year: int = 2025) -> pd.DataFrame: - # Uprate age targets from 2020, taxable income targets from 2021, employment income targets from 2023. - # Use PolicyEngine uprating factors. - from policyengine_uk_data.datasets import FRS_2020_21 - - sim = Microsimulation(dataset=FRS_2020_21) - matrix_20, y_20, _ = create_local_authority_target_matrix( - FRS_2020_21, 2020, uprate=False - ) - matrix_21, y_21, _ = create_local_authority_target_matrix( - FRS_2020_21, 2021, uprate=False - ) - matrix_23, y_23, _ = create_local_authority_target_matrix( - FRS_2020_21, 2023, uprate=False - ) - matrix_final, y_final, _ = create_local_authority_target_matrix( - FRS_2020_21, target_year, uprate=False - ) - weights_20 = sim.calculate("household_weight", 2020) - weights_21 = sim.calculate("household_weight", 2021) - weights_23 = sim.calculate("household_weight", 2023) - weights_final = sim.calculate("household_weight", target_year) - - rel_change_20_final = (weights_final @ matrix_final) / ( - weights_20 @ matrix_20 - ) - 1 - is_uprated_from_2020 = [ - col.startswith("age/") for col in matrix_20.columns - ] - uprating_from_2020 = np.zeros_like(matrix_20.columns, dtype=float) - uprating_from_2020[is_uprated_from_2020] = rel_change_20_final[ - is_uprated_from_2020 - ] - uprating = uprating_from_2020 - y = y * (1 + uprating) - - return y diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/README.md b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/README.md similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/README.md rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/README.md diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/age.csv b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/age.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/age.csv rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/age.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/create_employment_incomes.py b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/create_employment_incomes.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/create_employment_incomes.py rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/create_employment_incomes.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/create_total_incomes.py b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/create_total_incomes.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/create_total_incomes.py rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/create_total_incomes.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/employment_income.csv b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/employment_income.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/employment_income.csv rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/employment_income.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/fill_missing_age_demographics.py b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/fill_missing_age_demographics.py similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/fill_missing_age_demographics.py rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/fill_missing_age_demographics.py diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/nomis_earning_jobs_data.xlsx b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/nomis_earning_jobs_data.xlsx similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/nomis_earning_jobs_data.xlsx rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/nomis_earning_jobs_data.xlsx diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/spi_by_la.csv b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/spi_by_la.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/spi_by_la.csv rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/spi_by_la.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/spi_la_raw.csv b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/spi_la_raw.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/spi_la_raw.csv rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/spi_la_raw.csv diff --git a/policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/total_income.csv b/policyengine_uk_data/datasets/local_areas/local_authorities/targets/total_income.csv similarity index 100% rename from policyengine_uk_data/datasets/frs/local_areas/local_authorities/targets/total_income.csv rename to policyengine_uk_data/datasets/local_areas/local_authorities/targets/total_income.csv diff --git a/policyengine_uk_data/datasets/spi.py b/policyengine_uk_data/datasets/spi.py index b317f0427..41985f9e8 100644 --- a/policyengine_uk_data/datasets/spi.py +++ b/policyengine_uk_data/datasets/spi.py @@ -2,123 +2,115 @@ from policyengine_uk_data.storage import STORAGE_FOLDER import pandas as pd import numpy as np - - -class SPI(Dataset): - spi_data_file_path: str - data_format = Dataset.TIME_PERIOD_ARRAYS - - def generate(self): - df = pd.read_csv(self.spi_data_file_path, delimiter="\t") - - data = {} - data["person_id"] = df.SREF - for id_column in [ - "person_household_id", - "person_benunit_id", - "benunit_id", - "household_id", - ]: - data[id_column] = data["person_id"] - - data["state_id"] = np.ones(len(df), dtype=int) - data["person_state_id"] = data["state_id"] - - data["household_weight"] = df.FACT - data["dividend_income"] = df.DIVIDENDS - data["gift_aid"] = df.GIFTAID - data["region"] = ( - df.GORCODE.map( - { - 1: "NORTH_EAST", - 2: "NORTH_WEST", - 3: "YORKSHIRE", - 4: "EAST_MIDLANDS", - 5: "WEST_MIDLANDS", - 6: "EAST_OF_ENGLAND", - 7: "LONDON", - 8: "SOUTH_EAST", - 9: "SOUTH_WEST", - 10: "WALES", - 11: "SCOTLAND", - 12: "NORTHERN_IRELAND", - } - ) - .fillna("UNKNOWN") - .astype("S") +from policyengine_uk.data import UKSingleYearDataset + + +def create_spi( + spi_data_file_path: str, fiscal_year: int, output_file_path: str +) -> UKSingleYearDataset: + df = pd.read_csv(spi_data_file_path, delimiter="\t") + + person = pd.DataFrame() + benunit = pd.DataFrame() + household = pd.DataFrame() + person["person_id"] = df.SREF + person["person_household_id"] = df.SREF + person["person_benunit_id"] = df.SREF + benunit["benunit_id"] = df.SREF + household["household_id"] = df.SREF + + household["household_weight"] = df.FACT + person["dividend_income"] = df.DIVIDENDS + person["gift_aid"] = df.GIFTAID + person["region"] = ( + df.GORCODE.map( + { + 1: "NORTH_EAST", + 2: "NORTH_WEST", + 3: "YORKSHIRE", + 4: "EAST_MIDLANDS", + 5: "WEST_MIDLANDS", + 6: "EAST_OF_ENGLAND", + 7: "LONDON", + 8: "SOUTH_EAST", + 9: "SOUTH_WEST", + 10: "WALES", + 11: "SCOTLAND", + 12: "NORTHERN_IRELAND", + } ) - data["savings_interest_income"] = df.INCBBS - data["property_income"] = df.INCPROP - data["employment_income"] = df.PAY + df.EPB - data["employment_expenses"] = df.EXPS - data["private_pension_income"] = df.PENSION - # The below underestimates those with high amounts of excess pension - # savings, as it does not include the Annual Allowance - data["private_pension_contributions"] = df.PSAV_XS - data["pension_contributions_relief"] = df.PENSRLF - data["self_employment_income"] = df.PROFITS - # HMRC seems to assume the trading and property allowance are already deducted - # (per record inspection of SREF 15494988 in 2020-21) - data["trading_allowance"] = np.zeros(len(df)) - data["property_allowance"] = np.zeros(len(df)) - data["savings_starter_rate_income"] = np.zeros(len(df)) - data["capital_allowances"] = df.CAPALL - data["loss_relief"] = df.LOSSBF - - AGE_RANGES = { - -1: (16, 70), - 1: (16, 25), - 2: (25, 35), - 3: (35, 45), - 4: (45, 55), - 5: (55, 65), - 6: (65, 74), - 7: (74, 90), - } - age_range = df.AGERANGE - - # Randomly assign ages in age ranges - - percent_along_age_range = np.random.rand(len(df)) - min_age = np.array([AGE_RANGES[age][0] for age in age_range]) - max_age = np.array([AGE_RANGES[age][1] for age in age_range]) - data["age"] = ( - min_age + (max_age - min_age) * percent_along_age_range - ).astype(int) - - data["state_pension_reported"] = df.SRP - data["other_tax_credits"] = df.TAX_CRED - data["miscellaneous_income"] = ( - df.MOTHINC - + df.INCPBEN - + df.OSSBEN - + df.TAXTERM - + df.UBISJA - + df.OTHERINC - ) - data["gift_aid"] = df.GIFTAID + df.GIFTINV - data["other_investment_income"] = df.OTHERINV - data["covenanted_payments"] = df.COVNTS - data["other_deductions"] = df.MOTHDED + df.DEFICIEN - data["married_couples_allowance"] = df.MCAS - data["blind_persons_allowance"] = df.BPADUE - data["marriage_allowance"] = np.where(df.MAIND == 1, 1_250, 0) - - for field in data: - data[field] = {self.time_period: data[field]} - - self.save_dataset(data) - - -class SPI_2020_21(SPI): - spi_data_file_path = ( - "/Users/nikhilwoodruff/Downloads/UKDA-9121-tab/tab/put2021uk.tab" + .fillna("UNKNOWN") + .astype("S") + ) + person["savings_interest_income"] = df.INCBBS + person["property_income"] = df.INCPROP + person["employment_income"] = df.PAY + df.EPB + person["employment_expenses"] = df.EXPS + person["private_pension_income"] = df.PENSION + # The below underestimates those with high amounts of excess pension + # savings, as it does not include the Annual Allowance + person["private_pension_contributions"] = df.PSAV_XS + person["pension_contributions_relief"] = df.PENSRLF + person["self_employment_income"] = df.PROFITS + # HMRC seems to assume the trading and property allowance are already deducted + # (per record inspection of SREF 15494988 in 2020-21) + person["trading_allowance"] = np.zeros(len(df)) + person["property_allowance"] = np.zeros(len(df)) + person["savings_starter_rate_income"] = np.zeros(len(df)) + person["capital_allowances"] = df.CAPALL + person["loss_relief"] = df.LOSSBF + + AGE_RANGES = { + -1: (16, 70), + 1: (16, 25), + 2: (25, 35), + 3: (35, 45), + 4: (45, 55), + 5: (55, 65), + 6: (65, 74), + 7: (74, 90), + } + age_range = df.AGERANGE + + # Randomly assign ages in age ranges + + percent_along_age_range = np.random.rand(len(df)) + min_age = np.array([AGE_RANGES[age][0] for age in age_range]) + max_age = np.array([AGE_RANGES[age][1] for age in age_range]) + person["age"] = ( + min_age + (max_age - min_age) * percent_along_age_range + ).astype(int) + + person["state_pension_reported"] = df.SRP + person["other_tax_credits"] = df.TAX_CRED + person["miscellaneous_income"] = ( + df.MOTHINC + + df.INCPBEN + + df.OSSBEN + + df.TAXTERM + + df.UBISJA + + df.OTHERINC + ) + person["gift_aid"] = df.GIFTAID + df.GIFTINV + person["other_investment_income"] = df.OTHERINV + person["covenanted_payments"] = df.COVNTS + person["other_deductions"] = df.MOTHDED + df.DEFICIEN + person["married_couples_allowance"] = df.MCAS + person["blind_persons_allowance"] = df.BPADUE + person["marriage_allowance"] = np.where(df.MAIND == 1, 1_250, 0) + + dataset = UKSingleYearDataset( + person=person, + benunit=benunit, + household=household, + fiscal_year=fiscal_year, ) - file_path = STORAGE_FOLDER / "spi_2020_21.h5" - label = "SPI 2020-21" - name = "spi_2020_21" - time_period = 2020 + return dataset if __name__ == "__main__": - SPI_2020_21().generate() + spi_data_file_path = STORAGE_FOLDER / "spi_2020_21" / "put2021uk.tab" + fiscal_year = 2020 + output_file_path = STORAGE_FOLDER / "spi_2020.h5" + spi = create_spi(spi_data_file_path, fiscal_year) + spi.save(output_file_path) diff --git a/policyengine_uk_data/storage/download_private_prerequisites.py b/policyengine_uk_data/storage/download_private_prerequisites.py index 0bf4d9cef..3bf4f34bf 100644 --- a/policyengine_uk_data/storage/download_private_prerequisites.py +++ b/policyengine_uk_data/storage/download_private_prerequisites.py @@ -1,6 +1,7 @@ from policyengine_uk_data.utils.huggingface import download, upload from pathlib import Path import zipfile +import warnings def extract_zipped_folder(folder): @@ -9,25 +10,72 @@ def extract_zipped_folder(folder): zip_ref.extractall(folder.parent / folder.stem) -FOLDER = Path(__file__).parent - -FILES = [ - "frs_2020_21.zip", - "frs_2022_23.zip", - "frs_2023_24.zip", - "lcfs_2021_22.zip", - "was_2006_20.zip", - "etb_1977_21.zip", - "spi_2020_21.zip", -] - -FILES = [FOLDER / file for file in FILES] - -for file in FILES: - download( - repo="policyengine/policyengine-uk-data", - repo_filename=file.name, - local_folder=file.parent, - ) - extract_zipped_folder(file) - file.unlink() +def download_prerequisites(): + """Download prerequisite data files from HuggingFace. + + This function downloads and extracts the required data files that are + typically obtained by running `make download`. + """ + folder = Path(__file__).parent + + files = [ + "frs_2020_21.zip", + "frs_2022_23.zip", + "frs_2023_24.zip", + "lcfs_2021_22.zip", + "was_2006_20.zip", + "etb_1977_21.zip", + "spi_2020_21.zip", + ] + + files = [folder / file for file in files] + + for file in files: + download( + repo="policyengine/policyengine-uk-data", + repo_filename=file.name, + local_folder=file.parent, + ) + extract_zipped_folder(file) + file.unlink() + + +def check_prerequisites(): + """Check if prerequisite data files/folders are present. + + Returns: + bool: True if all prerequisites are present, False otherwise. + """ + folder = Path(__file__).parent + + expected_folders = [ + "frs_2020_21", + "frs_2022_23", + "frs_2023_24", + "lcfs_2021_22", + "was_2006_20", + "etb_1977_21", + "spi_2020_21", + ] + + missing = [] + for folder_name in expected_folders: + if not (folder / folder_name).exists(): + missing.append(folder_name) + + if missing: + warnings.warn( + f"Missing prerequisite data folders: {', '.join(missing)}. " + f"Run `from policyengine_uk_data import download_prerequisites; download_prerequisites()` " + f"to download them.", + UserWarning, + stacklevel=3, + ) + return False + + return True + + +# Keep backwards compatibility for direct script execution +if __name__ == "__main__": + download_prerequisites() diff --git a/policyengine_uk_data/storage/migrate_to_uk_single_year_datasets.py b/policyengine_uk_data/storage/migrate_to_uk_single_year_datasets.py deleted file mode 100644 index da7f1d9f1..000000000 --- a/policyengine_uk_data/storage/migrate_to_uk_single_year_datasets.py +++ /dev/null @@ -1,19 +0,0 @@ -from policyengine_uk.data import UKSingleYearDataset -from policyengine_uk import Microsimulation -from policyengine_core.data import Dataset -from policyengine_uk_data.datasets import EnhancedFRS_2023_24, FRS_2023_24 - - -def migrate_to_uk_single_year_dataset(file_path: str): - sim = Microsimulation(dataset=Dataset.from_file(file_path)) - - single_year_dataset = UKSingleYearDataset.from_simulation( - sim, fiscal_year=2023 - ) - - single_year_dataset.save(file_path) - - -if __name__ == "__main__": - migrate_to_uk_single_year_dataset(FRS_2023_24.file_path) - migrate_to_uk_single_year_dataset(EnhancedFRS_2023_24.file_path) diff --git a/policyengine_uk_data/storage/nhs_consumption_by_age_gender.csv b/policyengine_uk_data/storage/nhs_consumption_by_age_gender.csv new file mode 100644 index 000000000..004086475 --- /dev/null +++ b/policyengine_uk_data/storage/nhs_consumption_by_age_gender.csv @@ -0,0 +1,253 @@ +Service,Gender,Age group,Metric,Total +AE,Female,0 years,Activity Count,212079 +AE,Female,01-04 years,Activity Count,505885 +AE,Female,05-09 years,Activity Count,347081 +AE,Female,10-14 years,Activity Count,371563 +AE,Female,15-19 years,Activity Count,483626 +AE,Female,20-24 years,Activity Count,617087 +AE,Female,25-29 years,Activity Count,611997 +AE,Female,30-34 years,Activity Count,560654 +AE,Female,35-39 years,Activity Count,480036 +AE,Female,40-44 years,Activity Count,402489 +AE,Female,45-49 years,Activity Count,421889 +AE,Female,50-54 years,Activity Count,448613 +AE,Female,55-59 years,Activity Count,418718 +AE,Female,60-64 years,Activity Count,357017 +AE,Female,65-69 years,Activity Count,331175 +AE,Female,70-74 years,Activity Count,384744 +AE,Female,75-79 years,Activity Count,369570 +AE,Female,80-84 years,Activity Count,378626 +AE,Female,85-89 years,Activity Count,327708 +AE,Female,90-94 years,Activity Count,195096 +AE,Female,95 years or older,Activity Count,73381 +AE,Male,0 years,Activity Count,266383 +AE,Male,01-04 years,Activity Count,651982 +AE,Male,05-09 years,Activity Count,411892 +AE,Male,10-14 years,Activity Count,443253 +AE,Male,15-19 years,Activity Count,409286 +AE,Male,20-24 years,Activity Count,499314 +AE,Male,25-29 years,Activity Count,520689 +AE,Male,30-34 years,Activity Count,499270 +AE,Male,35-39 years,Activity Count,452672 +AE,Male,40-44 years,Activity Count,401018 +AE,Male,45-49 years,Activity Count,421480 +AE,Male,50-54 years,Activity Count,437104 +AE,Male,55-59 years,Activity Count,415672 +AE,Male,60-64 years,Activity Count,363507 +AE,Male,65-69 years,Activity Count,334916 +AE,Male,70-74 years,Activity Count,376324 +AE,Male,75-79 years,Activity Count,341874 +AE,Male,80-84 years,Activity Count,323601 +AE,Male,85-89 years,Activity Count,242458 +AE,Male,90-94 years,Activity Count,114545 +AE,Male,95 years or older,Activity Count,31993 +AE,Female,0 years,Total Cost,30148770.7 +AE,Female,01-04 years,Total Cost,73303426.7 +AE,Female,05-09 years,Total Cost,49003152.2 +AE,Female,10-14 years,Total Cost,55261204.8 +AE,Female,15-19 years,Total Cost,79355311 +AE,Female,20-24 years,Total Cost,101677604 +AE,Female,25-29 years,Total Cost,103591677 +AE,Female,30-34 years,Total Cost,96833952.2 +AE,Female,35-39 years,Total Cost,85119455.3 +AE,Female,40-44 years,Total Cost,73111612.4 +AE,Female,45-49 years,Total Cost,79541655 +AE,Female,50-54 years,Total Cost,86739022 +AE,Female,55-59 years,Total Cost,83554003.1 +AE,Female,60-64 years,Total Cost,74921474.2 +AE,Female,65-69 years,Total Cost,74205741.3 +AE,Female,70-74 years,Total Cost,92209066.3 +AE,Female,75-79 years,Total Cost,94937123.5 +AE,Female,80-84 years,Total Cost,104581887 +AE,Female,85-89 years,Total Cost,96823888.9 +AE,Female,90-94 years,Total Cost,60486434.5 +AE,Female,95 years or older,Total Cost,23345611.4 +AE,Male,0 years,Total Cost,38597370.1 +AE,Male,01-04 years,Total Cost,95191584.1 +AE,Male,05-09 years,Total Cost,58855565.3 +AE,Male,10-14 years,Total Cost,67069862.1 +AE,Male,15-19 years,Total Cost,68299159.1 +AE,Male,20-24 years,Total Cost,83483710.5 +AE,Male,25-29 years,Total Cost,88653812.2 +AE,Male,30-34 years,Total Cost,87093334.1 +AE,Male,35-39 years,Total Cost,80745741.5 +AE,Male,40-44 years,Total Cost,74135948.3 +AE,Male,45-49 years,Total Cost,80953801.4 +AE,Male,50-54 years,Total Cost,88215789.8 +AE,Male,55-59 years,Total Cost,86921818.8 +AE,Male,60-64 years,Total Cost,80659276.5 +AE,Male,65-69 years,Total Cost,79633763.7 +AE,Male,70-74 years,Total Cost,94195978.6 +AE,Male,75-79 years,Total Cost,90252429.9 +AE,Male,80-84 years,Total Cost,90053985.2 +AE,Male,85-89 years,Total Cost,70865034.3 +AE,Male,90-94 years,Total Cost,34791109 +AE,Male,95 years or older,Total Cost,9951869.71 +APC,Female,0 years,Activity Count,179975 +APC,Female,01-04 years,Activity Count,166080 +APC,Female,05-09 years,Activity Count,119129 +APC,Female,10-14 years,Activity Count,116854 +APC,Female,15-19 years,Activity Count,220700 +APC,Female,20-24 years,Activity Count,417022 +APC,Female,25-29 years,Activity Count,604916 +APC,Female,30-34 years,Activity Count,659361 +APC,Female,35-39 years,Activity Count,521586 +APC,Female,40-44 years,Activity Count,373350 +APC,Female,45-49 years,Activity Count,418570 +APC,Female,50-54 years,Activity Count,504566 +APC,Female,55-59 years,Activity Count,591516 +APC,Female,60-64 years,Activity Count,546883 +APC,Female,65-69 years,Activity Count,593173 +APC,Female,70-74 years,Activity Count,745088 +APC,Female,75-79 years,Activity Count,716489 +APC,Female,80-84 years,Activity Count,701215 +APC,Female,85-89 years,Activity Count,575906 +APC,Female,90-94 years,Activity Count,326043 +APC,Female,95 years or older,Activity Count,116671 +APC,Male,0 years,Activity Count,231010 +APC,Male,01-04 years,Activity Count,227129 +APC,Male,05-09 years,Activity Count,150286 +APC,Male,10-14 years,Activity Count,124152 +APC,Male,15-19 years,Activity Count,138695 +APC,Male,20-24 years,Activity Count,165402 +APC,Male,25-29 years,Activity Count,201294 +APC,Male,30-34 years,Activity Count,228088 +APC,Male,35-39 years,Activity Count,250474 +APC,Male,40-44 years,Activity Count,265449 +APC,Male,45-49 years,Activity Count,345386 +APC,Male,50-54 years,Activity Count,449294 +APC,Male,55-59 years,Activity Count,597189 +APC,Male,60-64 years,Activity Count,591735 +APC,Male,65-69 years,Activity Count,671518 +APC,Male,70-74 years,Activity Count,833479 +APC,Male,75-79 years,Activity Count,762975 +APC,Male,80-84 years,Activity Count,679676 +APC,Male,85-89 years,Activity Count,476440 +APC,Male,90-94 years,Activity Count,211073 +APC,Male,95 years or older,Activity Count,52974 +APC,Female,0 years,Total Cost,249777365 +APC,Female,01-04 years,Total Cost,217088968 +APC,Female,05-09 years,Total Cost,177517727 +APC,Female,10-14 years,Total Cost,216733472 +APC,Female,15-19 years,Total Cost,320270378 +APC,Female,20-24 years,Total Cost,601289475 +APC,Female,25-29 years,Total Cost,972425428 +APC,Female,30-34 years,Total Cost,1135682570 +APC,Female,35-39 years,Total Cost,867881313 +APC,Female,40-44 years,Total Cost,553715118 +APC,Female,45-49 years,Total Cost,603048703 +APC,Female,50-54 years,Total Cost,754837523 +APC,Female,55-59 years,Total Cost,847924810 +APC,Female,60-64 years,Total Cost,877315571 +APC,Female,65-69 years,Total Cost,991517578 +APC,Female,70-74 years,Total Cost,1281142417 +APC,Female,75-79 years,Total Cost,1289140495 +APC,Female,80-84 years,Total Cost,1349578327 +APC,Female,85-89 years,Total Cost,1185018283 +APC,Female,90-94 years,Total Cost,704541248 +APC,Female,95 years or older,Total Cost,255392422 +APC,Male,0 years,Total Cost,324223190 +APC,Male,01-04 years,Total Cost,294910749 +APC,Male,05-09 years,Total Cost,227643007 +APC,Male,10-14 years,Total Cost,227920739 +APC,Male,15-19 years,Total Cost,236394221 +APC,Male,20-24 years,Total Cost,244330615 +APC,Male,25-29 years,Total Cost,288740865 +APC,Male,30-34 years,Total Cost,316268020 +APC,Male,35-39 years,Total Cost,354154798 +APC,Male,40-44 years,Total Cost,387154627 +APC,Male,45-49 years,Total Cost,524862509 +APC,Male,50-54 years,Total Cost,723066778 +APC,Male,55-59 years,Total Cost,932563200 +APC,Male,60-64 years,Total Cost,1031083117 +APC,Male,65-69 years,Total Cost,1188794886 +APC,Male,70-74 years,Total Cost,1482408081 +APC,Male,75-79 years,Total Cost,1378990921 +APC,Male,80-84 years,Total Cost,1274704997 +APC,Male,85-89 years,Total Cost,940697209 +APC,Male,90-94 years,Total Cost,432169323 +APC,Male,95 years or older,Total Cost,109644790 +OP,Female,0 years,Activity Count,325441 +OP,Female,01-04 years,Activity Count,719275 +OP,Female,05-09 years,Activity Count,897665 +OP,Female,10-14 years,Activity Count,969630 +OP,Female,15-19 years,Activity Count,1156991 +OP,Female,20-24 years,Activity Count,1755334 +OP,Female,25-29 years,Activity Count,2950592 +OP,Female,30-34 years,Activity Count,3530495 +OP,Female,35-39 years,Activity Count,2899647 +OP,Female,40-44 years,Activity Count,2043206 +OP,Female,45-49 years,Activity Count,2228921 +OP,Female,50-54 years,Activity Count,2686270 +OP,Female,55-59 years,Activity Count,2823953 +OP,Female,60-64 years,Activity Count,2693294 +OP,Female,65-69 years,Activity Count,2751613 +OP,Female,70-74 years,Activity Count,3163861 +OP,Female,75-79 years,Activity Count,2672250 +OP,Female,80-84 years,Activity Count,2162918 +OP,Female,85-89 years,Activity Count,1348139 +OP,Female,90-94 years,Activity Count,532412 +OP,Female,95 years or older,Activity Count,137316 +OP,Male,0 years,Activity Count,401692 +OP,Male,01-04 years,Activity Count,947015 +OP,Male,05-09 years,Activity Count,1079069 +OP,Male,10-14 years,Activity Count,1033931 +OP,Male,15-19 years,Activity Count,896856 +OP,Male,20-24 years,Activity Count,709694 +OP,Male,25-29 years,Activity Count,851016 +OP,Male,30-34 years,Activity Count,956355 +OP,Male,35-39 years,Activity Count,1046825 +OP,Male,40-44 years,Activity Count,1121153 +OP,Male,45-49 years,Activity Count,1465178 +OP,Male,50-54 years,Activity Count,1886549 +OP,Male,55-59 years,Activity Count,2283433 +OP,Male,60-64 years,Activity Count,2456193 +OP,Male,65-69 years,Activity Count,2699217 +OP,Male,70-74 years,Activity Count,3186785 +OP,Male,75-79 years,Activity Count,2687074 +OP,Male,80-84 years,Activity Count,2079196 +OP,Male,85-89 years,Activity Count,1161187 +OP,Male,90-94 years,Activity Count,372921 +OP,Male,95 years or older,Activity Count,68484 +OP,Female,0 years,Total Cost,58872702.9 +OP,Female,01-04 years,Total Cost,121444400 +OP,Female,05-09 years,Total Cost,146984131 +OP,Female,10-14 years,Total Cost,164019001 +OP,Female,15-19 years,Total Cost,179659867 +OP,Female,20-24 years,Total Cost,242131906 +OP,Female,25-29 years,Total Cost,407477772 +OP,Female,30-34 years,Total Cost,488197443 +OP,Female,35-39 years,Total Cost,409246386 +OP,Female,40-44 years,Total Cost,298827477 +OP,Female,45-49 years,Total Cost,325925802 +OP,Female,50-54 years,Total Cost,388913382 +OP,Female,55-59 years,Total Cost,402521375 +OP,Female,60-64 years,Total Cost,380833755 +OP,Female,65-69 years,Total Cost,386525748 +OP,Female,70-74 years,Total Cost,440767051 +OP,Female,75-79 years,Total Cost,367629876 +OP,Female,80-84 years,Total Cost,293296393 +OP,Female,85-89 years,Total Cost,177694021 +OP,Female,90-94 years,Total Cost,68202176.6 +OP,Female,95 years or older,Total Cost,16859358 +OP,Male,0 years,Total Cost,74301400.9 +OP,Male,01-04 years,Total Cost,163252103 +OP,Male,05-09 years,Total Cost,183189795 +OP,Male,10-14 years,Total Cost,178808739 +OP,Male,15-19 years,Total Cost,141828786 +OP,Male,20-24 years,Total Cost,99340346.1 +OP,Male,25-29 years,Total Cost,118503476 +OP,Male,30-34 years,Total Cost,135535727 +OP,Male,35-39 years,Total Cost,149377324 +OP,Male,40-44 years,Total Cost,160115100 +OP,Male,45-49 years,Total Cost,209730217 +OP,Male,50-54 years,Total Cost,271532785 +OP,Male,55-59 years,Total Cost,325197136 +OP,Male,60-64 years,Total Cost,347698137 +OP,Male,65-69 years,Total Cost,379726112 +OP,Male,70-74 years,Total Cost,442751873 +OP,Male,75-79 years,Total Cost,370982792 +OP,Male,80-84 years,Total Cost,282006082 +OP,Male,85-89 years,Total Cost,154559700 +OP,Male,90-94 years,Total Cost,48058882.9 +OP,Male,95 years or older,Total Cost,8573041.95 \ No newline at end of file diff --git a/policyengine_uk_data/storage/upload_completed_datasets.py b/policyengine_uk_data/storage/upload_completed_datasets.py index db3cf7e63..b110bb6e8 100644 --- a/policyengine_uk_data/storage/upload_completed_datasets.py +++ b/policyengine_uk_data/storage/upload_completed_datasets.py @@ -1,14 +1,11 @@ -from policyengine_uk_data.datasets import EnhancedFRS_2023_24, FRS_2023_24 from policyengine_uk_data.storage import STORAGE_FOLDER from policyengine_uk_data.utils.data_upload import upload_data_files def upload_datasets(): dataset_files = [ - FRS_2023_24.file_path, - EnhancedFRS_2023_24.file_path, - STORAGE_FOLDER / "frs_2023_29.h5", - STORAGE_FOLDER / "enhanced_frs_2023_29.h5", + STORAGE_FOLDER / "frs_2023_24.h5", + STORAGE_FOLDER / "enhanced_frs_2023_24.h5", STORAGE_FOLDER / "parliamentary_constituency_weights.h5", STORAGE_FOLDER / "local_authority_weights.h5", ] diff --git a/policyengine_uk_data/storage/uprating_factors.csv b/policyengine_uk_data/storage/uprating_factors.csv index b8a123cb3..62ab4dacb 100644 --- a/policyengine_uk_data/storage/uprating_factors.csv +++ b/policyengine_uk_data/storage/uprating_factors.csv @@ -1,44 +1,84 @@ Variable,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034 -afcs,1.0,1.017,1.024,1.054,1.238,1.238,1.238,1.238,1.238,1.238,1.238,1.238,1.238,1.238,1.238 -alcohol_and_tobacco_consumption,1.0,1.021,1.136,1.263,1.295,1.318,1.352,1.39,1.424,1.424,1.424,1.424,1.424,1.424,1.424 -capital_gains,1.0,1.14,1.322,0.996,1.033,1.043,1.2,1.307,1.435,1.435,1.435,1.435,1.435,1.435,1.435 -childcare_expenses,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -clothing_and_footwear_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -communication_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -corporate_wealth,1.0,1.0,0.891,0.895,0.922,0.951,0.984,1.018,1.054,1.054,1.054,1.054,1.054,1.054,1.054 -council_tax,1.0,1.0,1.028,1.1,1.157,1.215,1.28,1.347,1.347,1.347,1.347,1.347,1.347,1.347,1.347 -diesel_spending,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -dividend_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -domestic_energy_consumption,1.0,1.121,2.025,1.971,1.521,1.404,1.318,1.351,1.404,1.404,1.404,1.404,1.404,1.404,1.404 -education_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -employment_income,1.0,1.06,1.121,1.193,1.23,1.253,1.28,1.309,1.344,1.344,1.344,1.344,1.344,1.344,1.344 -employment_income_before_lsr,1.0,1.06,1.121,1.193,1.23,1.253,1.28,1.309,1.344,1.344,1.344,1.344,1.344,1.344,1.344 -food_and_non_alcoholic_beverages_consumption,1.0,1.021,1.136,1.263,1.295,1.318,1.352,1.39,1.424,1.424,1.424,1.424,1.424,1.424,1.424 -gross_financial_wealth,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -health_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -household_furnishings_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -household_weight,1.0,1.004,1.007,1.01,1.015,1.018,1.021,1.024,1.024,1.024,1.024,1.024,1.024,1.024,1.024 -housing_water_and_electricity_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -main_residence_value,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -miscellaneous_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -miscellaneous_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -mortgage_capital_repayment,1.0,1.09,1.181,1.172,1.145,1.15,1.18,1.222,1.267,1.267,1.267,1.267,1.267,1.267,1.267 -mortgage_interest_repayment,1.0,0.985,1.139,1.631,2.087,2.056,2.132,2.254,2.358,2.358,2.358,2.358,2.358,2.358,2.358 -net_financial_wealth,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -non_residential_property_value,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -other_residential_property_value,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -owned_land,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -pension_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -petrol_spending,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -private_pension_contributions,1.0,1.06,1.121,1.193,1.23,1.253,1.28,1.309,1.344,1.344,1.344,1.344,1.344,1.344,1.344 -private_pension_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -property_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -recreation_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -rent,1.0,1.013,1.034,1.08,1.118,1.124,1.124,1.139,1.161,1.161,1.161,1.161,1.161,1.161,1.161 -restaurants_and_hotels_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -savings,1.0,1.014,0.904,0.908,0.935,0.965,0.998,1.032,1.069,1.069,1.069,1.069,1.069,1.069,1.069 -savings_interest_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -self_employment_income,1.0,1.03,1.053,1.116,1.159,1.206,1.259,1.316,1.378,1.378,1.378,1.378,1.378,1.378,1.378 -state_pension,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 -sublet_income,1.0,1.072,1.241,1.338,1.333,1.409,1.473,1.587,1.709,1.709,1.709,1.709,1.709,1.709,1.709 -transport_consumption,1.0,1.039,1.144,1.209,1.229,1.248,1.269,1.294,1.32,1.32,1.32,1.32,1.32,1.32,1.32 +afcs_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +alcohol_and_tobacco_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +attendance_allowance_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +benunit_rent,1.0,1.0,1.0,1.11,1.184,1.223,1.275,1.312,1.351,1.392,1.392,1.392,1.392,1.392,1.392 +bsp_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +capital_gains,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +capital_gains_before_response,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +carers_allowance_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +child_benefit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +child_tax_credit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +childcare_expenses,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +clothing_and_footwear_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +communication_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +corporate_wealth,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +diesel_spending,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +dividend_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +dla_m_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +dla_sc_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +domestic_energy_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +education_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +employee_pension_contributions,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +employer_pension_contributions,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +employment_income,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +employment_income_before_lsr,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +esa_contrib_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +esa_income_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +food_and_non_alcoholic_beverages_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +free_school_fruit_veg,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +free_school_meals,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +free_school_milk,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +gross_financial_wealth,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +health_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +household_furnishings_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +household_weight,1.0,1.0,1.003,1.017,1.027,1.039,1.046,1.054,1.058,1.064,1.064,1.064,1.064,1.064,1.064 +housing_benefit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +housing_service_charges,1.0,1.0,1.0,1.064,1.137,1.191,1.235,1.262,1.289,1.318,1.318,1.318,1.318,1.318,1.318 +housing_water_and_electricity_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +iidb_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +incapacity_benefit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +income_support_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +jsa_contrib_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +jsa_income_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +lump_sum_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +main_residence_value,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +maintenance_expenses,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +maintenance_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +maternity_allowance_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +miscellaneous_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +miscellaneous_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +mortgage_capital_repayment,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +mortgage_interest_repayment,1.0,1.0,1.262,1.874,2.288,2.599,2.927,3.167,3.3,3.455,3.455,3.455,3.455,3.455,3.455 +net_financial_wealth,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +non_residential_property_value,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +other_investment_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +other_residential_property_value,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +owned_land,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +pension_credit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +pension_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +personal_pension_contributions,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +petrol_spending,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +pip_dl_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +pip_m_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +private_pension_income,1.0,1.003,1.053,1.106,1.161,1.216,1.261,1.288,1.315,1.346,1.346,1.346,1.346,1.346,1.346 +private_transfer_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +property_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +recreation_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +restaurants_and_hotels_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +savings,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +savings_interest_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +sda_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +self_employment_income,1.0,1.0,1.063,1.089,1.141,1.194,1.231,1.27,1.315,1.365,1.365,1.365,1.365,1.365,1.365 +state_pension,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +state_pension_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +statutory_maternity_pay,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +statutory_paternity_pay,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +statutory_sick_pay,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +student_loan_repayments,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +sublet_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384 +transport_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +universal_credit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +water_and_sewerage_charges,1.0,1.0,1.0,1.092,1.14,1.21,1.283,1.349,1.4,1.46,1.46,1.46,1.46,1.46,1.46 +winter_fuel_allowance_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 +working_tax_credit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 diff --git a/policyengine_uk_data/storage/uprating_growth_factors.csv b/policyengine_uk_data/storage/uprating_growth_factors.csv index 861fc4816..eb1bcca2b 100644 --- a/policyengine_uk_data/storage/uprating_growth_factors.csv +++ b/policyengine_uk_data/storage/uprating_growth_factors.csv @@ -1,44 +1,84 @@ Variable,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034 -afcs,0,0.017,0.007,0.029,0.175,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 -alcohol_and_tobacco_consumption,0,0.021,0.113,0.112,0.025,0.018,0.026,0.028,0.024,0.0,0.0,0.0,0.0,0.0,0.0 -capital_gains,0,0.14,0.16,-0.247,0.037,0.01,0.151,0.089,0.098,0.0,0.0,0.0,0.0,0.0,0.0 -childcare_expenses,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -clothing_and_footwear_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -communication_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -corporate_wealth,0,0.0,-0.109,0.004,0.03,0.031,0.035,0.035,0.035,0.0,0.0,0.0,0.0,0.0,0.0 -council_tax,0,0.0,0.028,0.07,0.052,0.05,0.053,0.052,0.0,0.0,0.0,0.0,0.0,0.0,0.0 -diesel_spending,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -dividend_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -domestic_energy_consumption,0,0.121,0.806,-0.027,-0.228,-0.077,-0.061,0.025,0.039,0.0,0.0,0.0,0.0,0.0,0.0 -education_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -employment_income,0,0.06,0.058,0.064,0.031,0.019,0.022,0.023,0.027,0.0,0.0,0.0,0.0,0.0,0.0 -employment_income_before_lsr,0,0.06,0.058,0.064,0.031,0.019,0.022,0.023,0.027,0.0,0.0,0.0,0.0,0.0,0.0 -food_and_non_alcoholic_beverages_consumption,0,0.021,0.113,0.112,0.025,0.018,0.026,0.028,0.024,0.0,0.0,0.0,0.0,0.0,0.0 -gross_financial_wealth,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -health_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -household_furnishings_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -household_weight,0,0.004,0.003,0.003,0.005,0.003,0.003,0.003,0.0,0.0,0.0,0.0,0.0,0.0,0.0 -housing_water_and_electricity_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -main_residence_value,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -miscellaneous_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -miscellaneous_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -mortgage_capital_repayment,0,0.09,0.083,-0.008,-0.023,0.004,0.026,0.036,0.037,0.0,0.0,0.0,0.0,0.0,0.0 -mortgage_interest_repayment,0,-0.015,0.156,0.432,0.28,-0.015,0.037,0.057,0.046,0.0,0.0,0.0,0.0,0.0,0.0 -net_financial_wealth,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -non_residential_property_value,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -other_residential_property_value,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -owned_land,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -pension_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -petrol_spending,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -private_pension_contributions,0,0.06,0.058,0.064,0.031,0.019,0.022,0.023,0.027,0.0,0.0,0.0,0.0,0.0,0.0 -private_pension_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -property_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -recreation_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -rent,0,0.013,0.021,0.044,0.035,0.005,0.0,0.013,0.019,0.0,0.0,0.0,0.0,0.0,0.0 -restaurants_and_hotels_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -savings,0,0.014,-0.108,0.004,0.03,0.032,0.034,0.034,0.036,0.0,0.0,0.0,0.0,0.0,0.0 -savings_interest_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -self_employment_income,0,0.03,0.022,0.06,0.039,0.041,0.044,0.045,0.047,0.0,0.0,0.0,0.0,0.0,0.0 -state_pension,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 -sublet_income,0,0.072,0.158,0.078,-0.004,0.057,0.045,0.077,0.077,0.0,0.0,0.0,0.0,0.0,0.0 -transport_consumption,0,0.039,0.101,0.057,0.017,0.015,0.017,0.02,0.02,0.0,0.0,0.0,0.0,0.0,0.0 +afcs_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +alcohol_and_tobacco_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +attendance_allowance_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +benunit_rent,0,0.0,0.0,0.11,0.067,0.033,0.043,0.029,0.03,0.03,0.0,0.0,0.0,0.0,0.0 +bsp_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +capital_gains,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +capital_gains_before_response,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +carers_allowance_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +child_benefit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +child_tax_credit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +childcare_expenses,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +clothing_and_footwear_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +communication_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +corporate_wealth,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +diesel_spending,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +dividend_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +dla_m_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +dla_sc_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +domestic_energy_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +education_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +employee_pension_contributions,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +employer_pension_contributions,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +employment_income,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +employment_income_before_lsr,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +esa_contrib_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +esa_income_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +food_and_non_alcoholic_beverages_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +free_school_fruit_veg,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +free_school_meals,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +free_school_milk,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +gross_financial_wealth,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +health_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +household_furnishings_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +household_weight,0,0.0,0.003,0.014,0.01,0.012,0.007,0.008,0.004,0.006,0.0,0.0,0.0,0.0,0.0 +housing_benefit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +housing_service_charges,0,0.0,0.0,0.064,0.069,0.047,0.037,0.022,0.021,0.022,0.0,0.0,0.0,0.0,0.0 +housing_water_and_electricity_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +iidb_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +incapacity_benefit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +income_support_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +jsa_contrib_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +jsa_income_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +lump_sum_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +main_residence_value,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +maintenance_expenses,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +maintenance_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +maternity_allowance_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +miscellaneous_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +miscellaneous_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +mortgage_capital_repayment,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +mortgage_interest_repayment,0,0.0,0.262,0.485,0.221,0.136,0.126,0.082,0.042,0.047,0.0,0.0,0.0,0.0,0.0 +net_financial_wealth,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +non_residential_property_value,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +other_investment_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +other_residential_property_value,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +owned_land,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +pension_credit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +pension_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +personal_pension_contributions,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +petrol_spending,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +pip_dl_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +pip_m_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +private_pension_income,0,0.003,0.05,0.05,0.05,0.047,0.037,0.021,0.021,0.024,0.0,0.0,0.0,0.0,0.0 +private_transfer_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +property_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +recreation_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +restaurants_and_hotels_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +savings,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +savings_interest_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +sda_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +self_employment_income,0,0.0,0.063,0.024,0.048,0.046,0.031,0.032,0.035,0.038,0.0,0.0,0.0,0.0,0.0 +state_pension,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +state_pension_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +statutory_maternity_pay,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +statutory_paternity_pay,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +statutory_sick_pay,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +student_loan_repayments,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +sublet_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0 +transport_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +universal_credit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +water_and_sewerage_charges,0,0.0,0.0,0.092,0.044,0.061,0.06,0.051,0.038,0.043,0.0,0.0,0.0,0.0,0.0 +winter_fuel_allowance_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 +working_tax_credit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 diff --git a/policyengine_uk_data/tests/conftest.py b/policyengine_uk_data/tests/conftest.py new file mode 100644 index 000000000..21a2e53b3 --- /dev/null +++ b/policyengine_uk_data/tests/conftest.py @@ -0,0 +1,29 @@ +import pytest +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk_data.storage import STORAGE_FOLDER + + +@pytest.fixture +def frs(): + """FRS dataset for testing.""" + try: + return UKSingleYearDataset(STORAGE_FOLDER / "frs_2023_24.h5") + except FileNotFoundError: + pytest.skip("FRS dataset not available") + + +@pytest.fixture +def enhanced_frs(): + """Enhanced FRS dataset for testing.""" + try: + return UKSingleYearDataset(STORAGE_FOLDER / "enhanced_frs_2023_24.h5") + except FileNotFoundError: + pytest.skip("Enhanced FRS dataset not available") + + +@pytest.fixture +def baseline(enhanced_frs: UKSingleYearDataset): + """Baseline microsimulation using enhanced FRS.""" + from policyengine_uk import Microsimulation + + return Microsimulation(dataset=enhanced_frs) diff --git a/policyengine_uk_data/tests/microsimulation/reforms_config.yaml b/policyengine_uk_data/tests/microsimulation/reforms_config.yaml index 4a13bccd6..718e79526 100644 --- a/policyengine_uk_data/tests/microsimulation/reforms_config.yaml +++ b/policyengine_uk_data/tests/microsimulation/reforms_config.yaml @@ -1,10 +1,10 @@ reforms: - name: Raise basic rate by 1pp - expected_impact: 7.7 + expected_impact: 7.5 parameters: gov.hmrc.income_tax.rates.uk[0].rate: 0.21 - name: Raise higher rate by 1pp - expected_impact: 5.0 + expected_impact: 5.5 parameters: gov.hmrc.income_tax.rates.uk[1].rate: 0.42 - name: Raise personal allowance by ~800GBP/year @@ -12,22 +12,22 @@ reforms: parameters: gov.hmrc.income_tax.allowances.personal_allowance.amount: 13000 - name: Raise child benefit by 25GBP/week per additional child - expected_impact: -1.2 + expected_impact: -1.3 parameters: gov.hmrc.child_benefit.amount.additional: 25 - name: Reduce Universal Credit taper rate to 20% - expected_impact: -36.0 + expected_impact: -34.8 parameters: gov.dwp.universal_credit.means_test.reduction_rate: 0.2 - name: Raise Class 1 main employee NICs rate to 10% - expected_impact: 12.5 + expected_impact: 12.6 parameters: gov.hmrc.national_insurance.class_1.rates.employee.main: 0.1 - name: Raise VAT standard rate by 2pp - expected_impact: 19.7 + expected_impact: 21.1 parameters: gov.hmrc.vat.standard_rate: 0.22 - name: Raise additional rate by 3pp - expected_impact: 4.6 + expected_impact: 4.3 parameters: gov.hmrc.income_tax.rates.uk[2].rate: 0.48 diff --git a/policyengine_uk_data/tests/microsimulation/test_reform_impacts.py b/policyengine_uk_data/tests/microsimulation/test_reform_impacts.py index d9fe6558c..c9b006dfc 100644 --- a/policyengine_uk_data/tests/microsimulation/test_reform_impacts.py +++ b/policyengine_uk_data/tests/microsimulation/test_reform_impacts.py @@ -18,14 +18,8 @@ reforms_data = config["reforms"] -dataset = Dataset.from_file(STORAGE_FOLDER / "enhanced_frs_2023_24.h5") - -# Initialize baseline simulation -baseline = Microsimulation(dataset=dataset) - - -def get_fiscal_impact(reform: dict) -> float: +def get_fiscal_impact(baseline, enhanced_frs, reform: dict) -> float: """ Calculate the fiscal impact of a reform in billions. @@ -36,7 +30,7 @@ def get_fiscal_impact(reform: dict) -> float: Fiscal impact in billions (positive = revenue increase) """ baseline_revenue = baseline.calculate("gov_balance", 2029).sum() - reform_simulation = Microsimulation(reform=reform, dataset=dataset) + reform_simulation = Microsimulation(reform=reform, dataset=enhanced_frs) reform_revenue = reform_simulation.calculate("gov_balance", 2029).sum() return (reform_revenue - baseline_revenue) / 1e9 @@ -55,9 +49,11 @@ def get_fiscal_impact(reform: dict) -> float: test_params, ids=reform_names, ) -def test_reform_fiscal_impacts(reform, reform_name, expected_impact): +def test_reform_fiscal_impacts( + baseline, enhanced_frs, reform, reform_name, expected_impact +): """Test that each reform produces the expected fiscal impact.""" - impact = get_fiscal_impact(reform) + impact = get_fiscal_impact(baseline, enhanced_frs, reform) # Allow for small numerical differences (1.0 billion tolerance) assert ( diff --git a/policyengine_uk_data/tests/test_aggregates.py b/policyengine_uk_data/tests/test_aggregates.py index 9906f36f5..1186acb17 100644 --- a/policyengine_uk_data/tests/test_aggregates.py +++ b/policyengine_uk_data/tests/test_aggregates.py @@ -1,5 +1,3 @@ -from policyengine_uk import Microsimulation -from policyengine_uk_data.datasets import EnhancedFRS_2022_23 import pytest AGGREGATES = { @@ -9,13 +7,12 @@ "bus_subsidy_spending": 2.5e9, } -# Initialize simulation -sim = Microsimulation(dataset=EnhancedFRS_2022_23) - @pytest.mark.parametrize("variable", AGGREGATES.keys()) -def test_aggregates(variable: str): - estimate = sim.calculate(variable, map_to="household", period=2025).sum() +def test_aggregates(baseline, variable: str): + estimate = baseline.calculate( + variable, map_to="household", period=2025 + ).sum() assert ( abs(estimate / AGGREGATES[variable] - 1) < 0.7 diff --git a/policyengine_uk_data/tests/test_child_limit.py b/policyengine_uk_data/tests/test_child_limit.py index 542bdc6b9..e01039824 100644 --- a/policyengine_uk_data/tests/test_child_limit.py +++ b/policyengine_uk_data/tests/test_child_limit.py @@ -1,29 +1,27 @@ -def test_child_limit(): - from policyengine_uk import Microsimulation - from policyengine_uk_data.datasets import EnhancedFRS_2022_23 - - # Initialize simulation - sim = Microsimulation(dataset=EnhancedFRS_2022_23) - +def test_child_limit(baseline): child_is_affected = ( - sim.map_result( - sim.calculate( + baseline.map_result( + baseline.calculate( "uc_is_child_limit_affected", map_to="household", period=2025 ), "household", "person", ) > 0 - ) * sim.calculate("is_child", map_to="person").values + ) * baseline.calculate("is_child", map_to="person").values child_in_uc_household = ( - sim.calculate("universal_credit", map_to="person", period=2025).values + baseline.calculate( + "universal_credit", map_to="person", period=2025 + ).values > 0 ) - children_in_capped_households = sim.map_result( + children_in_capped_households = baseline.map_result( child_is_affected * child_in_uc_household, "person", "household" ) capped_households = (children_in_capped_households > 0) * 1.0 - household_weight = sim.calculate("household_weight", period=2025).values + household_weight = baseline.calculate( + "household_weight", period=2025 + ).values children_affected = ( children_in_capped_households * household_weight ).sum() diff --git a/policyengine_uk_data/tests/test_childcare.py b/policyengine_uk_data/tests/test_childcare.py index 8815c15b3..6305e8be2 100644 --- a/policyengine_uk_data/tests/test_childcare.py +++ b/policyengine_uk_data/tests/test_childcare.py @@ -1,6 +1,4 @@ -def test_childcare(): - from policyengine_uk import Microsimulation - from policyengine_uk_data.datasets import EnhancedFRS_2022_23 +def test_childcare(baseline, enhanced_frs): import numpy as np # Define targets (same as in the optimization script) @@ -19,11 +17,8 @@ def test_childcare(): }, } - # Initialize simulation - sim = Microsimulation(dataset=EnhancedFRS_2022_23) - # Calculate dataframe with all required variables - df = sim.calculate_dataframe( + df = baseline.calculate_dataframe( [ "age", "tax_free_childcare", @@ -45,12 +40,16 @@ def test_childcare(): # Calculate actual spending values spending = { - "tfc": sim.calculate("tax_free_childcare", 2024).sum() / 1e9, - "extended": sim.calculate("extended_childcare_entitlement", 2024).sum() + "tfc": baseline.calculate("tax_free_childcare", 2024).sum() / 1e9, + "extended": baseline.calculate( + "extended_childcare_entitlement", 2024 + ).sum() / 1e9, - "targeted": sim.calculate("targeted_childcare_entitlement", 2024).sum() + "targeted": baseline.calculate( + "targeted_childcare_entitlement", 2024 + ).sum() / 1e9, - "universal": sim.calculate( + "universal": baseline.calculate( "universal_childcare_entitlement", 2024 ).sum() / 1e9, @@ -98,7 +97,7 @@ def test_childcare(): for key in targets["spending"]: target_spending = targets["spending"][key] ratio = spending[key] / target_spending - passed = abs(ratio - 1) < 0.3 + passed = abs(ratio - 1) < 0.5 status = "✓" if passed else "✗" print( f"{key.upper():<12} {spending[key]:<10.3f} {target_spending:<10.3f} {ratio:<10.3f} {status:<10}" @@ -116,7 +115,7 @@ def test_childcare(): for key in targets["caseload"]: target_caseload = targets["caseload"][key] ratio = caseload[key] / target_caseload - passed = abs(ratio - 1) < 0.3 + passed = abs(ratio - 1) < 0.5 status = "✓" if passed else "✗" print( f"{key.upper():<12} {caseload[key]:<10.1f} {target_caseload:<10.1f} {ratio:<10.3f} {status:<10}" diff --git a/policyengine_uk_data/tests/test_population.py b/policyengine_uk_data/tests/test_population.py index 6a748fcaa..c3c0d1b6e 100644 --- a/policyengine_uk_data/tests/test_population.py +++ b/policyengine_uk_data/tests/test_population.py @@ -1,11 +1,5 @@ -def test_child_limit(): - from policyengine_uk import Microsimulation - from policyengine_uk_data.datasets import EnhancedFRS_2022_23 - - # Initialize simulation - sim = Microsimulation(dataset=EnhancedFRS_2022_23) - - population = sim.calculate("people", 2025).sum() / 1e6 +def test_population(baseline): + population = baseline.calculate("people", 2025).sum() / 1e6 POPULATION_TARGET = 69.5 # Expected UK population in millions, per ONS 2022-based estimate here: https://www.ons.gov.uk/peoplepopulationandcommunity/populationandmigration/populationprojections/bulletins/nationalpopulationprojections/2022based assert ( abs(population / POPULATION_TARGET - 1) < 0.02 diff --git a/policyengine_uk_data/utils/__init__.py b/policyengine_uk_data/utils/__init__.py index 97b914e80..68be03302 100644 --- a/policyengine_uk_data/utils/__init__.py +++ b/policyengine_uk_data/utils/__init__.py @@ -1,5 +1,5 @@ -from .github import * from .uprating import * from .datasets import * from .loss import * from .qrf import * +from .progress import * diff --git a/policyengine_uk_data/utils/calibrate.py b/policyengine_uk_data/utils/calibrate.py new file mode 100644 index 000000000..46964234f --- /dev/null +++ b/policyengine_uk_data/utils/calibrate.py @@ -0,0 +1,282 @@ +import torch +from policyengine_uk import Microsimulation +import pandas as pd +import numpy as np +import h5py +from policyengine_uk_data.storage import STORAGE_FOLDER +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk_data.utils.progress import ProcessingProgress + + +def calibrate_local_areas( + dataset: UKSingleYearDataset, + matrix_fn, + national_matrix_fn, + area_count: int, + weight_file: str, + dataset_key: str = "2025", + epochs: int = 256, + excluded_training_targets=[], + log_csv=None, + verbose: bool = False, + area_name: str = "area", + get_performance=None, + nested_progress=None, +): + """ + Generic calibration function for local areas (constituencies, local authorities, etc.) + + Args: + dataset: The dataset to calibrate + matrix_fn: Function that returns (matrix, targets, mask) for the local areas + national_matrix_fn: Function that returns (matrix, targets) for national totals + area_count: Number of areas (e.g., 650 for constituencies, 360 for local authorities) + weight_file: Name of the h5 file to save weights to + dataset_key: Key to use in the h5 file + epochs: Number of training epochs + excluded_training_targets: List of targets to exclude from training (for validation) + log_csv: CSV file to log performance to + verbose: Whether to print progress + area_name: Name of the area type for logging + """ + dataset = dataset.copy() + matrix, y, r = matrix_fn(dataset) + m_c, y_c = matrix.copy(), y.copy() + m_national, y_national = national_matrix_fn(dataset) + m_n, y_n = m_national.copy(), y_national.copy() + + # Weights - area_count x num_households + original_weights = np.log( + dataset.household.household_weight.values / area_count + + np.random.random(len(dataset.household.household_weight.values)) + * 0.01 + ) + weights = torch.tensor( + np.ones((area_count, len(original_weights))) * original_weights, + dtype=torch.float32, + requires_grad=True, + ) + + # Set up validation targets if specified + validation_targets_local = ( + matrix.columns.isin(excluded_training_targets) + if hasattr(matrix, "columns") + else None + ) + validation_targets_national = ( + m_national.columns.isin(excluded_training_targets) + if hasattr(m_national, "columns") + else None + ) + dropout_targets = len(excluded_training_targets) > 0 + + # Convert to tensors + metrics = torch.tensor( + matrix.values if hasattr(matrix, "values") else matrix, + dtype=torch.float32, + ) + y = torch.tensor( + y.values if hasattr(y, "values") else y, dtype=torch.float32 + ) + matrix_national = torch.tensor( + m_national.values if hasattr(m_national, "values") else m_national, + dtype=torch.float32, + ) + y_national = torch.tensor( + y_national.values if hasattr(y_national, "values") else y_national, + dtype=torch.float32, + ) + r = torch.tensor(r, dtype=torch.float32) + + def sre(x, y): + one_way = ((1 + x) / (1 + y) - 1) ** 2 + other_way = ((1 + y) / (1 + x) - 1) ** 2 + return torch.min(one_way, other_way) + + def loss(w, validation: bool = False): + pred_local = (w.unsqueeze(-1) * metrics.unsqueeze(0)).sum(dim=1) + if dropout_targets and validation_targets_local is not None: + if validation: + mask = validation_targets_local + else: + mask = ~validation_targets_local + pred_local = pred_local[:, mask] + mse_local = torch.mean(sre(pred_local, y[:, mask])) + else: + mse_local = torch.mean(sre(pred_local, y)) + + pred_national = (w.sum(axis=0) * matrix_national.T).sum(axis=1) + if dropout_targets and validation_targets_national is not None: + if validation: + mask = validation_targets_national + else: + mask = ~validation_targets_national + pred_national = pred_national[mask] + mse_national = torch.mean(sre(pred_national, y_national[mask])) + else: + mse_national = torch.mean(sre(pred_national, y_national)) + + return mse_local + mse_national + + def pct_close(w, t=0.1, local=True, national=True): + """Return the percentage of metrics that are within t% of the target""" + numerator = 0 + denominator = 0 + + if local: + pred_local = (w.unsqueeze(-1) * metrics.unsqueeze(0)).sum(dim=1) + e_local = torch.sum( + torch.abs((pred_local / (1 + y) - 1)) < t + ).item() + c_local = pred_local.shape[0] * pred_local.shape[1] + numerator += e_local + denominator += c_local + + if national: + pred_national = (w.sum(axis=0) * matrix_national.T).sum(axis=1) + e_national = torch.sum( + torch.abs((pred_national / (1 + y_national) - 1)) < t + ).item() + c_national = pred_national.shape[0] + numerator += e_national + denominator += c_national + + return numerator / denominator + + def dropout_weights(weights, p): + if p == 0: + return weights + # Replace p% of the weights with the mean value of the rest of them + mask = torch.rand_like(weights) < p + mean = weights[~mask].mean() + masked_weights = weights.clone() + masked_weights[mask] = mean + return masked_weights + + optimizer = torch.optim.Adam([weights], lr=1e-1) + final_weights = (torch.exp(weights) * r).detach().numpy() + performance = pd.DataFrame() + + progress_tracker = ProcessingProgress() if verbose else None + + if verbose and progress_tracker: + with progress_tracker.track_calibration( + epochs, nested_progress + ) as update_calibration: + for epoch in range(epochs): + update_calibration(epoch + 1, calculating_loss=True) + + optimizer.zero_grad() + weights_ = torch.exp(dropout_weights(weights, 0.05)) * r + l = loss(weights_) + l.backward() + optimizer.step() + + local_close = pct_close(weights_, local=True, national=False) + national_close = pct_close( + weights_, local=False, national=True + ) + + if dropout_targets: + validation_loss = loss(weights_, validation=True) + update_calibration( + epoch + 1, + loss_value=validation_loss.item(), + calculating_loss=False, + ) + else: + update_calibration( + epoch + 1, loss_value=l.item(), calculating_loss=False + ) + + if epoch % 10 == 0: + final_weights = (torch.exp(weights) * r).detach().numpy() + + # Log performance if requested and get_performance function is available + if log_csv: + performance_step = get_performance( + final_weights, + m_c, + y_c, + m_n, + y_n, + excluded_training_targets, + ) + performance_step["epoch"] = epoch + performance_step["loss"] = ( + performance_step.rel_abs_error**2 + ) + performance_step["target_name"] = [ + f"{area}/{metric}" + for area, metric in zip( + performance_step.name, performance_step.metric + ) + ] + performance = pd.concat( + [performance, performance_step], ignore_index=True + ) + performance.to_csv(log_csv, index=False) + + # Save weights + with h5py.File(STORAGE_FOLDER / weight_file, "w") as f: + f.create_dataset(dataset_key, data=final_weights) + + dataset.household.household_weight = final_weights.sum( + axis=0 + ) + else: + for epoch in range(epochs): + optimizer.zero_grad() + weights_ = torch.exp(dropout_weights(weights, 0.05)) * r + l = loss(weights_) + l.backward() + optimizer.step() + + local_close = pct_close(weights_, local=True, national=False) + national_close = pct_close(weights_, local=False, national=True) + + if verbose and (epoch % 1 == 0): + if dropout_targets: + validation_loss = loss(weights_, validation=True) + print( + f"Training loss: {l.item():,.3f}, Validation loss: {validation_loss.item():,.3f}, Epoch: {epoch}, " + f"{area_name}<10%: {local_close:.1%}, National<10%: {national_close:.1%}" + ) + else: + print( + f"Loss: {l.item()}, Epoch: {epoch}, {area_name}<10%: {local_close:.1%}, National<10%: {national_close:.1%}" + ) + + if epoch % 10 == 0: + final_weights = (torch.exp(weights) * r).detach().numpy() + + # Log performance if requested and get_performance function is available + if log_csv: + performance_step = get_performance( + final_weights, + m_c, + y_c, + m_n, + y_n, + excluded_training_targets, + ) + performance_step["epoch"] = epoch + performance_step["loss"] = performance_step.rel_abs_error**2 + performance_step["target_name"] = [ + f"{area}/{metric}" + for area, metric in zip( + performance_step.name, performance_step.metric + ) + ] + performance = pd.concat( + [performance, performance_step], ignore_index=True + ) + performance.to_csv(log_csv, index=False) + + # Save weights + with h5py.File(STORAGE_FOLDER / weight_file, "w") as f: + f.create_dataset(dataset_key, data=final_weights) + + dataset.household.household_weight = final_weights.sum(axis=0) + + return dataset diff --git a/policyengine_uk_data/utils/create_multi_year_dataset.py b/policyengine_uk_data/utils/create_multi_year_dataset.py deleted file mode 100644 index 1f199cb0f..000000000 --- a/policyengine_uk_data/utils/create_multi_year_dataset.py +++ /dev/null @@ -1,59 +0,0 @@ -from policyengine_uk.data import UKMultiYearDataset, UKSingleYearDataset -from policyengine_uk.data.economic_assumptions import apply_uprating -from policyengine_uk import Microsimulation -from policyengine_uk_data.storage import STORAGE_FOLDER -from policyengine_core.data import Dataset - - -def convert_legacy_to_multi_year_dataset( - file_path: str, - new_file_path: str, - start_year: int = 2023, - end_year: int = 2029, -) -> UKMultiYearDataset: - """ - Convert a legacy single year dataset to a multi-year dataset. - """ - sim = Microsimulation(dataset=Dataset.from_file(file_path)) - - dataset = UKSingleYearDataset.from_simulation(sim, fiscal_year=start_year) - dataset.time_period = str(start_year) - - datasets = [dataset] - - for year in range(start_year + 1, end_year + 1): - dataset = dataset.copy() - dataset.time_period = str(year) - - datasets.append(dataset.copy()) - - multi_year_dataset = UKMultiYearDataset( - datasets=datasets, - ) - multi_year_dataset = apply_uprating(multi_year_dataset) - - multi_year_dataset.save(new_file_path) - - -if __name__ == "__main__": - file_paths = [ - STORAGE_FOLDER / "frs_2023_24.h5", - STORAGE_FOLDER / "enhanced_frs_2023_24.h5", - ] - out_file_paths = [ - STORAGE_FOLDER / "frs_2023_29.h5", - STORAGE_FOLDER / "enhanced_frs_2023_29.h5", - ] - - for file_path, new_file_path in zip(file_paths, out_file_paths): - convert_legacy_to_multi_year_dataset( - file_path=str(file_path), - new_file_path=str(new_file_path), - start_year=2023, - end_year=2029, - ) - print(f"Converted {file_path} to {new_file_path}") - # Test here - sim = Microsimulation( - dataset=UKMultiYearDataset(file_path=str(new_file_path)) - ) diff --git a/policyengine_uk_data/utils/datasets.py b/policyengine_uk_data/utils/datasets.py index 0969a62c2..82c9e2733 100644 --- a/policyengine_uk_data/utils/datasets.py +++ b/policyengine_uk_data/utils/datasets.py @@ -12,7 +12,7 @@ def sum_to_entity( values: pd.Series, foreign_key: pd.Series, primary_key -) -> pd.Series: +) -> np.ndarray: """Sums values by joining foreign and primary keys. Args: @@ -23,7 +23,9 @@ def sum_to_entity( Returns: pd.Series: A value for each person. """ - return values.groupby(foreign_key).sum().reindex(primary_key).fillna(0) + return ( + values.groupby(foreign_key).sum().reindex(primary_key).fillna(0).values + ) def categorical( @@ -40,11 +42,7 @@ def categorical( Returns: pd.Series: The mapped values. """ - return ( - values.fillna(default) - .map({i: j for i, j in zip(left, right)}) - .astype("S") - ) + return values.fillna(default).map({i: j for i, j in zip(left, right)}) def sum_from_positive_fields( diff --git a/policyengine_uk_data/utils/github.py b/policyengine_uk_data/utils/github.py deleted file mode 100644 index d949ac890..000000000 --- a/policyengine_uk_data/utils/github.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -import requests -from tqdm import tqdm -import time - -auth_headers = { - "Authorization": f"token {os.environ.get('POLICYENGINE_UK_DATA_GITHUB_TOKEN')}", -} - - -def get_asset_url( - org: str, repo: str, release_tag: str, file_name: str -) -> str: - url = f"https://api.github.com/repos/{org}/{repo}/releases/tags/{release_tag}" - response = requests.get(url, headers=auth_headers) - if response.status_code != 200: - raise ValueError( - f"Invalid response code {response.status_code} for url {url}." - ) - assets = response.json()["assets"] - for asset in assets: - if asset["name"] == file_name: - return asset["url"] - else: - raise ValueError( - f"File {file_name} not found in release {release_tag} of {org}/{repo}." - ) - - -def get_release_id(org: str, repo: str, release_tag: str) -> int: - url = f"https://api.github.com/repos/{org}/{repo}/releases/tags/{release_tag}" - response = requests.get(url, headers=auth_headers) - if response.status_code != 200: - raise ValueError( - f"Invalid response code {response.status_code} for url {url}." - ) - return response.json()["id"] - - -def download( - org: str, repo: str, release_tag: str, file_name: str, file_path: str -) -> bytes: - - url = get_asset_url(org, repo, release_tag, file_name) - - response = requests.get( - url, - headers={ - "Accept": "application/octet-stream", - **auth_headers, - }, - ) - - if response.status_code != 200: - raise ValueError( - f"Invalid response code {response.status_code} for url {url}." - ) - - with open(file_path, "wb") as f: - f.write(response.content) - - -def upload( - org: str, repo: str, release_tag: str, file_name: str, file_path: str -) -> bytes: - release_id = get_release_id(org, repo, release_tag) - - # First, list release assets - url = f"https://api.github.com/repos/{org}/{repo}/releases/{release_id}/assets" - response = requests.get(url, headers=auth_headers).json() - names = [asset["name"] for asset in response] - if file_name in names: - print( - f"Asset {file_name} already exists in release {release_tag} of {org}/{repo}, skipping." - ) - return - - url = f"https://uploads.github.com/repos/{org}/{repo}/releases/{release_id}/assets?name={file_name}" - - headers = { - "Accept": "application/vnd.github.v3+json", - "Content-Type": "application/octet-stream", - **auth_headers, - } - - with open(file_path, "rb") as f: - data = f.read() - - response = requests.post( - url, - headers=headers, - data=data, - ) - - if response.status_code != 201: - raise ValueError( - f"Invalid response code {response.status_code} for url {url}. Received: {response.text}" - ) - - return response.json() - - -def set_pr_auto_review_comment(text: str): - # On a pull request, set a review comment with the given text. - - pr_number = os.environ["GITHUB_PR_NUMBER"] - - url = f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/pulls/{pr_number}/reviews" - - response = requests.post( - url, - headers=auth_headers, - json={ - "body": text, - "event": "COMMENT", - }, - ) - - if response.status_code != 200: - raise ValueError( - f"Invalid response code {response.status_code} for url {url}. Received: {response.text}" - ) diff --git a/policyengine_uk_data/utils/imputations/income.py b/policyengine_uk_data/utils/imputations/income.py deleted file mode 100644 index 9110a5b43..000000000 --- a/policyengine_uk_data/utils/imputations/income.py +++ /dev/null @@ -1,102 +0,0 @@ -import pandas as pd -from pathlib import Path -import numpy as np -from policyengine_uk_data.storage import STORAGE_FOLDER - -SPI_TAB_FOLDER = STORAGE_FOLDER / "spi_2020_21" -SPI_RENAMES = dict( - private_pension_income="PENSION", - self_employment_income="PROFITS", - property_income="INCPROP", - savings_interest_income="INCBBS", - dividend_income="DIVIDENDS", - blind_persons_allowance="BPADUE", - married_couples_allowance="MCAS", - gift_aid="GIFTAID", - capital_allowances="CAPALL", - deficiency_relief="DEFICIEN", - covenanted_payments="COVNTS", - charitable_investment_gifts="GIFTINV", - employment_expenses="EPB", - other_deductions="MOTHDED", - person_weight="FACT", - benunit_weight="FACT", - household_weight="FACT", - state_pension="SRP", -) - - -def generate_spi_table(spi: pd.DataFrame): - LOWER = np.array([0, 16, 25, 35, 45, 55, 65, 75]) - UPPER = np.array([16, 25, 35, 45, 55, 65, 75, 80]) - age_range = spi.AGERANGE - spi["age"] = LOWER[age_range] + np.random.rand(len(spi)) * ( - UPPER[age_range] - LOWER[age_range] - ) - - REGIONS = { - 1: "NORTH_EAST", - 2: "NORTH_WEST", - 3: "YORKSHIRE", - 4: "EAST_MIDLANDS", - 5: "WEST_MIDLANDS", - 6: "EAST_OF_ENGLAND", - 7: "LONDON", - 8: "SOUTH_EAST", - 9: "SOUTH_WEST", - 10: "WALES", - 11: "SCOTLAND", - 12: "NORTHERN_IRELAND", - } - - spi["region"] = np.array([REGIONS.get(x, "LONDON") for x in spi.GORCODE]) - - spi["gender"] = np.where(spi.SEX == 1, "MALE", "FEMALE") - - for rename in SPI_RENAMES: - spi[rename] = spi[SPI_RENAMES[rename]] - - spi["employment_income"] = spi[["PAY", "EPB", "TAXTERM"]].sum(axis=1) - - spi = spi[spi.TI > 500_000] - - return spi - - -PREDICTORS = [ - "age", - "gender", - "region", -] - -IMPUTATIONS = [ - "employment_income", - "self_employment_income", - "savings_interest_income", - "dividend_income", - "private_pension_income", - "employment_expenses", - "property_income", - "gift_aid", -] - - -def save_imputation_models(): - from policyengine_uk_data.utils import QRF - - income = QRF() - spi = pd.read_csv(SPI_TAB_FOLDER / "put2021uk.tab", delimiter="\t") - spi = generate_spi_table(spi) - spi = spi[PREDICTORS + IMPUTATIONS] - income.fit(spi[PREDICTORS], spi[IMPUTATIONS]) - income.save(STORAGE_FOLDER / "income.pkl") - - -def create_income_model(overwrite_existing: bool = False): - if (STORAGE_FOLDER / "income.pkl").exists() and not overwrite_existing: - return - save_imputation_models() - - -if __name__ == "__main__": - create_income_model() diff --git a/policyengine_uk_data/utils/imputations/public_services.py b/policyengine_uk_data/utils/imputations/public_services.py deleted file mode 100644 index 4b818c1cc..000000000 --- a/policyengine_uk_data/utils/imputations/public_services.py +++ /dev/null @@ -1,107 +0,0 @@ -from policyengine_uk import Microsimulation -import pandas as pd -import numpy as np -from policyengine_uk_data.utils.qrf import QRF - -sim = Microsimulation( - dataset="hf://policyengine/policyengine-uk-data/enhanced_frs_2022_23.h5" -) - -df = sim.calculate_dataframe( - [ - "household_weight", - "household_id", - "is_adult", - "is_child", - "is_SP_age", - "dla", - "pip", - "hbai_household_net_income", - ], - period=2025, -) - -education = sim.calculate("current_education", period=2025) -df["count_primary_education"] = sim.map_result( - education == "PRIMARY", "person", "household" -) -df["count_secondary_education"] = sim.map_result( - education == "LOWER_SECONDARY", "person", "household" -) -df["count_further_education"] = sim.map_result( - education.isin(["UPPER_SECONDARY", "TERTIARY"]), "person", "household" -) - - -etb = pd.read_csv( - "~/Downloads/UKDA-8856-tab 2/tab/householdv2_1977-2021.tab", delimiter="\t" -) -etb = etb[etb.year == etb.year.max()] -etb = etb.replace(" ", np.nan) - -etb = etb[ - [ - "adults", - "childs", - "disinc", - "benk", - "educ", - "totnhs", - "rail", - "bussub", - "hsub", - "hhold_adj_weight", - "noretd", - "primed", - "secoed", - "wagern", - "welf", - "furted", - "disliv", - "pips", - ] -] -etb = etb.dropna().astype(float) -model = QRF() - -WEEKS_IN_YEAR = 52 - - -train = pd.DataFrame() -train["is_adult"] = etb.adults -train["is_child"] = etb.childs -train["hbai_household_net_income"] = etb.disinc * WEEKS_IN_YEAR -train["is_SP_age"] = etb.noretd -train["count_primary_education"] = etb.primed -train["count_secondary_education"] = etb.secoed -train["count_further_education"] = etb.furted -train["dla"] = etb.disliv -train["pip"] = etb.pips -train["public_service_in_kind_value"] = etb.benk * WEEKS_IN_YEAR -train["education_service_in_kind_value"] = etb.educ * WEEKS_IN_YEAR -train["nhs_in_kind_value"] = etb.totnhs * WEEKS_IN_YEAR -train["rail_subsidy_in_kind_value"] = etb.rail * WEEKS_IN_YEAR -train["bus_subsidy_in_kind_value"] = etb.bussub * WEEKS_IN_YEAR - -PREDICTORS = [ - "is_adult", - "is_child", - "is_SP_age", - "count_primary_education", - "count_secondary_education", - "count_further_education", - "dla", - "pip", - "hbai_household_net_income", -] -OUTPUTS = [ - "public_service_in_kind_value", - "education_service_in_kind_value", - "nhs_in_kind_value", - "rail_subsidy_in_kind_value", - "bus_subsidy_in_kind_value", -] - -model.fit(X=train[PREDICTORS], y=train[OUTPUTS]) - -outputs = model.predict(df[PREDICTORS]) diff --git a/policyengine_uk_data/utils/imputations/vat.py b/policyengine_uk_data/utils/imputations/vat.py deleted file mode 100644 index e197e9e89..000000000 --- a/policyengine_uk_data/utils/imputations/vat.py +++ /dev/null @@ -1,53 +0,0 @@ -import pandas as pd -from pathlib import Path -import numpy as np -from policyengine_uk_data.storage import STORAGE_FOLDER - -ETB_TAB_FOLDER = STORAGE_FOLDER / "etb_1977_21" - -CONSUMPTION_PCT_REDUCED_RATE = 0.03 # From OBR's VAT page -CURRENT_VAT_RATE = 0.2 - -PREDICTORS = ["is_adult", "is_child", "is_SP_age", "household_net_income"] -IMPUTATIONS = ["full_rate_vat_expenditure_rate"] - - -def generate_etb_table(etb: pd.DataFrame): - etb_2020 = etb[etb.year == 2020].dropna() - for col in etb_2020: - etb_2020[col] = pd.to_numeric(etb_2020[col], errors="coerce") - - etb_2020_df = pd.DataFrame() - etb_2020_df["is_adult"] = etb_2020.adults - etb_2020_df["is_child"] = etb_2020.childs - etb_2020_df["is_SP_age"] = etb_2020.noretd - etb_2020_df["household_net_income"] = etb_2020.disinc * 52 - etb_2020_df["full_rate_vat_expenditure_rate"] = ( - etb_2020.totvat * (1 - CONSUMPTION_PCT_REDUCED_RATE) / CURRENT_VAT_RATE - ) / (etb_2020.expdis - etb_2020.totvat) - return etb_2020_df[~etb_2020_df.full_rate_vat_expenditure_rate.isna()] - - -def save_imputation_models(): - from policyengine_uk_data.utils.qrf import QRF - - vat = QRF() - etb = pd.read_csv( - ETB_TAB_FOLDER / "householdv2_1977-2021.tab", - delimiter="\t", - low_memory=False, - ) - etb = generate_etb_table(etb) - etb = etb[PREDICTORS + IMPUTATIONS] - vat.fit(etb[PREDICTORS], etb[IMPUTATIONS]) - vat.save(STORAGE_FOLDER / "vat.pkl") - - -def create_vat_model(overwrite_existing: bool = False): - if (STORAGE_FOLDER / "vat.pkl").exists() and not overwrite_existing: - return - save_imputation_models() - - -if __name__ == "__main__": - create_vat_model() diff --git a/policyengine_uk_data/utils/loss.py b/policyengine_uk_data/utils/loss.py index 15d78540f..3c1af947a 100644 --- a/policyengine_uk_data/utils/loss.py +++ b/policyengine_uk_data/utils/loss.py @@ -1,7 +1,16 @@ +""" +Loss functions and target matrices for dataset calibration. + +This module creates target matrices comparing PolicyEngine UK model outputs +against official statistics from OBR, ONS, HMRC, DWP and other sources. +Used for calibrating household weights to match aggregate targets. +""" + import numpy as np import pandas as pd from policyengine_uk_data.storage import STORAGE_FOLDER from policyengine_uk_data.utils import uprate_values +from policyengine_uk.data import UKSingleYearDataset tax_benefit = pd.read_csv(STORAGE_FOLDER / "tax_benefit.csv") tax_benefit["name"] = tax_benefit["name"].apply(lambda x: f"obr/{x}") @@ -25,21 +34,39 @@ def create_target_matrix( - dataset: str, - time_period: str, + dataset: UKSingleYearDataset, + time_period: str = None, reform=None, ) -> np.ndarray: """ - Create a target matrix A, s.t. for household weights w, the target vector b and a perfectly calibrated PolicyEngine UK: + Create target matrix for calibration against official statistics. + + Creates a matrix A such that for household weights w, target vector b + and a perfectly calibrated PolicyEngine UK: A * w = b - A * w = b + Compares model outputs against: + - OBR tax and benefit aggregates + - ONS demographic and regional statistics + - HMRC income distribution data + - DWP benefit caseload data + - VOA council tax statistics + Args: + dataset: PolicyEngine UK dataset to analyse. + time_period: Year for target statistics (uses dataset default if None). + reform: Policy reform to apply during analysis. + + Returns: + Tuple of (target_matrix, target_values) for calibration. """ # First- tax-benefit outcomes from the DWP and OBR. from policyengine_uk import Microsimulation + if time_period is None: + time_period = dataset.time_period + sim = Microsimulation(dataset=dataset, reform=reform) sim.default_calculation_period = time_period @@ -111,7 +138,6 @@ def pe_count(*variables): df["obr/income_tax"] = pe("income_tax") df["obr/jobseekers_allowance"] = pe("jsa_income") + pe("jsa_contrib") df["obr/pension_credit"] = pe("pension_credit") - df["obr/stamp_duty_land_tax"] = pe("expected_sdlt") df["obr/state_pension"] = pe("state_pension") # df["obr/tax_credits"] = pe("tax_credits") df["obr/tv_licence_fee"] = pe("tv_licence") @@ -229,13 +255,12 @@ def pe_count(*variables): "private_pension_income", "property_income", "savings_interest_income", - "dividend_income", ] income_df = sim.calculate_dataframe(["total_income"] + INCOME_VARIABLES) incomes = pd.read_csv(STORAGE_FOLDER / "incomes_projection.csv") - incomes = incomes[incomes.year == time_period] + incomes = incomes[incomes.year.astype(str) == str(time_period)] for i, row in incomes.iterrows(): lower = row.total_income_lower_bound upper = row.total_income_upper_bound @@ -244,7 +269,9 @@ def pe_count(*variables): ) for variable in INCOME_VARIABLES: name_amount = ( - "hmrc/" + variable + f"_income_band_{i}_{lower:_}_to_{upper:_}" + "hmrc/" + + variable + + f"_income_band_{i}_{lower:_.0f}_to_{upper:_.0f}" ) df[name_amount] = household_from_person( income_df[variable] * in_income_band @@ -254,7 +281,7 @@ def pe_count(*variables): name_count = ( "hmrc/" + variable - + f"_count_income_band_{i}_{lower:_}_to_{upper:_}" + + f"_count_income_band_{i}_{lower:_.0f}_to_{upper:_.0f}" ) df[name_count] = household_from_person( (income_df[variable] > 0) * in_income_band @@ -323,7 +350,7 @@ def pe_count(*variables): for i, row in ct_data.iterrows(): selected_region = row["Region"] in_region = sim.calculate("region").values == selected_region - for band in ["A", "B", "C", "D", "E", "F", "G", "H", "I"]: + for band in ["A", "B", "C", "D", "E", "F", "G", "H"]: name = f"voa/council_tax/{selected_region}/{band}" in_band = sim.calculate("council_tax_band") == band df[name] = (in_band * in_region).astype(float) @@ -353,6 +380,18 @@ def pe_count(*variables): def get_loss_results( dataset, time_period, reform=None, household_weights=None ): + """ + Calculate loss metrics comparing model outputs to targets. + + Args: + dataset: PolicyEngine UK dataset to evaluate. + time_period: Year for comparison. + reform: Policy reform to apply. + household_weights: Custom weights (uses dataset weights if None). + + Returns: + DataFrame with estimate vs target comparisons and error metrics. + """ matrix, targets = create_target_matrix(dataset, time_period, reform) from policyengine_uk import Microsimulation diff --git a/policyengine_uk_data/utils/population.py b/policyengine_uk_data/utils/population.py new file mode 100644 index 000000000..48c362853 --- /dev/null +++ b/policyengine_uk_data/utils/population.py @@ -0,0 +1,18 @@ +from policyengine_uk.system import parameters + + +def get_population_growth_factor(start_year: int, end_year: int) -> float: + """ + Calculate the population growth factor between two years. + + Args: + start_year (int): The starting year. + end_year (int): The ending year. + + Returns: + float: The population growth factor. + """ + + population = parameters.gov.economic_assumptions.indices.ons.population + + return population(end_year) / population(start_year) diff --git a/policyengine_uk_data/utils/progress.py b/policyengine_uk_data/utils/progress.py new file mode 100644 index 000000000..ccb5efcf6 --- /dev/null +++ b/policyengine_uk_data/utils/progress.py @@ -0,0 +1,456 @@ +""" +Rich progress utilities for long-running operations. + +Provides nested progress bars with colors and enhanced formatting +for dataset processing, calibration, and other computationally intensive tasks. +""" + +from contextlib import contextmanager +from typing import Any, Dict, List, Optional, Union +import time + +from rich.console import Console +from rich.progress import ( + Progress, + TaskID, + BarColumn, + MofNCompleteColumn, + SpinnerColumn, + TaskProgressColumn, + TimeElapsedColumn, + TimeRemainingColumn, + TextColumn, +) +from rich.panel import Panel +from rich.table import Table +from rich.text import Text + + +class RichProgress: + """Rich progress manager with nested progress bars and custom styling.""" + + def __init__(self, console: Optional[Console] = None): + """Initialize progress manager. + + Args: + console: Rich console instance (creates new one if None). + """ + self.console = console or Console() + self.progress: Optional[Progress] = None + self.tasks: Dict[str, TaskID] = {} + self._active = False + + def __enter__(self): + """Start progress tracking.""" + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Stop progress tracking.""" + self.stop() + + def start(self): + """Initialize and start progress display.""" + if self._active: + return + + self.progress = Progress( + SpinnerColumn(), + TextColumn("[bold blue]{task.description}"), + BarColumn(bar_width=None), + MofNCompleteColumn(), + TaskProgressColumn(), + TextColumn("•"), + TimeElapsedColumn(), + TextColumn("•"), + TimeRemainingColumn(), + console=self.console, + expand=True, + ) + self.progress.start() + self._active = True + + def stop(self): + """Stop progress display.""" + if not self._active or not self.progress: + return + + self.progress.stop() + self._active = False + + def add_task( + self, + name: str, + description: str, + total: Optional[int] = None, + visible: bool = True, + ) -> str: + """Add a progress task. + + Args: + name: Unique task identifier. + description: Human-readable task description. + total: Total units of work (None for indeterminate). + visible: Whether task should be visible initially. + + Returns: + Task name for updating progress. + """ + if not self._active or not self.progress: + raise RuntimeError("Progress not started") + + task_id = self.progress.add_task( + description=description, + total=total, + visible=visible, + ) + self.tasks[name] = task_id + return name + + def update_task( + self, + name: str, + advance: Optional[int] = None, + completed: Optional[int] = None, + description: Optional[str] = None, + total: Optional[int] = None, + visible: Optional[bool] = None, + **kwargs: Any, + ): + """Update a progress task. + + Args: + name: Task identifier. + advance: Units to advance progress by. + completed: Set absolute completion amount. + description: Update task description. + total: Update total work units. + visible: Update visibility. + **kwargs: Additional update parameters. + """ + if not self._active or not self.progress or name not in self.tasks: + return + + task_id = self.tasks[name] + self.progress.update( + task_id, + advance=advance, + completed=completed, + description=description, + total=total, + visible=visible, + **kwargs, + ) + + def complete_task(self, name: str): + """Mark a task as completed.""" + if name in self.tasks and self._active and self.progress: + task_id = self.tasks[name] + task = self.progress.tasks[task_id] + if task.total is not None: + self.progress.update(task_id, completed=task.total) + + def remove_task(self, name: str): + """Remove a task from tracking.""" + if name in self.tasks and self._active and self.progress: + task_id = self.tasks[name] + self.progress.remove_task(task_id) + del self.tasks[name] + + +@contextmanager +def create_progress(console: Optional[Console] = None): + """Context manager for creating progress tracking. + + Args: + console: Rich console instance. + + Yields: + RichProgress instance. + """ + progress = RichProgress(console) + try: + progress.start() + yield progress + finally: + progress.stop() + + +class ProcessingProgress: + """Specialized progress tracker for data processing operations.""" + + def __init__(self, console: Optional[Console] = None): + """Initialize processing progress tracker. + + Args: + console: Rich console instance. + """ + self.console = console or Console() + self.progress_manager: Optional[RichProgress] = None + + @contextmanager + def track_dataset_creation(self, datasets: List[str]): + """Track dataset creation progress with stable display. + + Args: + datasets: List of dataset names to create. + + Yields: + Tuple of (update_dataset function, progress manager for nested tasks). + """ + with create_progress(self.console) as progress: + # Main dataset creation progress + main_task = progress.add_task( + "main_progress", + "Dataset creation", + total=len(datasets), + ) + + # Individual step tasks (hidden initially) + step_tasks = {} + for i, dataset in enumerate(datasets): + task_id = progress.add_task( + f"step_{i}", + f"[dim]{dataset}[/dim]", + total=1, + visible=False, + ) + step_tasks[dataset] = task_id + + current_step_index = 0 + + def update_dataset(dataset_name: str, status: str = "processing"): + """Update progress for a specific dataset.""" + nonlocal current_step_index + + if dataset_name in step_tasks: + task_id = step_tasks[dataset_name] + + if status == "processing": + # Show current step as active + progress.update_task( + task_id, + description=f"[yellow]●[/yellow] {dataset_name}", + visible=True, + ) + elif status == "completed": + # Mark as completed and advance main progress + progress.update_task( + task_id, + description=f"[green]✓[/green] {dataset_name}", + completed=1, + ) + progress.update_task(main_task, advance=1) + current_step_index += 1 + + # Update main task description with current status + completed_count = current_step_index + total_count = len(datasets) + progress.update_task( + main_task, + description=f"Dataset creation ([green]{completed_count}[/green]/{total_count} complete)", + ) + + # Return both the update function and the progress manager for nesting + yield update_dataset, progress + + @contextmanager + def track_calibration(self, iterations: int, nested_progress=None): + """Track calibration progress. + + Args: + iterations: Number of calibration iterations. + nested_progress: Existing progress manager to add calibration to. + + Yields: + Function to update calibration progress. + """ + if nested_progress: + # Add calibration as a nested task in existing progress + calibration_task = nested_progress.add_task( + "calibration_nested", + "Calibration", + total=iterations, + ) + + def update_calibration( + iteration: int, + loss_value: Optional[float] = None, + calculating_loss: bool = False, + ): + """Update calibration progress.""" + if calculating_loss: + nested_progress.update_task( + calibration_task, + description=f"[yellow]●[/yellow] Calibration epoch {iteration}/{iterations} • calculating loss", + ) + else: + loss_text = ( + f" • loss: {loss_value:.6f}" if loss_value else "" + ) + nested_progress.update_task( + calibration_task, + description=f"[blue]●[/blue] Calibration epoch {iteration}/{iterations}{loss_text}", + advance=1, + ) + + yield update_calibration + + else: + # Use standalone progress display + with create_progress(self.console) as progress: + main_task = progress.add_task( + "calibration", + "Running calibration", + total=iterations, + ) + + def update_calibration( + iteration: int, + loss_value: Optional[float] = None, + calculating_loss: bool = False, + ): + """Update calibration progress.""" + if calculating_loss: + progress.update_task( + main_task, + description=f"Calibration iteration {iteration}/{iterations} • [yellow]calculating loss[/yellow]", + ) + else: + loss_text = ( + f" • loss: {loss_value:.6f}" if loss_value else "" + ) + progress.update_task( + main_task, + description=f"Calibration iteration {iteration}/{iterations}{loss_text}", + advance=1, + ) + + yield update_calibration + + @contextmanager + def track_file_processing( + self, files: List[str], operation: str = "processing" + ): + """Track file processing operations. + + Args: + files: List of files to process. + operation: Description of operation being performed. + + Yields: + Function to update file processing progress. + """ + with create_progress(self.console) as progress: + main_task = progress.add_task( + "file_processing", + f"{operation.title()} {len(files)} files", + total=len(files), + ) + + def update_file( + filename: str, + status: str = "processing", + details: Optional[str] = None, + ): + """Update progress for a specific file.""" + details_text = f" • {details}" if details else "" + progress.update_task( + main_task, + description=f"{operation.title()} files • [blue]{filename}[/blue] ({status}){details_text}", + advance=1 if status == "completed" else 0, + ) + + yield update_file + + +def display_summary_table( + title: str, + data: List[Dict[str, Union[str, int, float]]], + console: Optional[Console] = None, +): + """Display a formatted summary table. + + Args: + title: Table title. + data: List of dictionaries with table data. + console: Rich console instance. + """ + if not data: + return + + console = console or Console() + + table = Table(title=title, show_header=True, header_style="bold magenta") + + if data: + for key in data[0].keys(): + table.add_column(key.replace("_", " ").title()) + + for row in data: + table.add_row(*[str(value) for value in row.values()]) + + console.print(table) + + +def display_error_panel( + error_message: str, + suggestions: Optional[List[str]] = None, + console: Optional[Console] = None, +): + """Display an error panel with suggestions. + + Args: + error_message: Main error message. + suggestions: List of suggested solutions. + console: Rich console instance. + """ + console = console or Console() + + content = Text(error_message, style="red") + + if suggestions: + content.append("\n\nSuggestions:\n", style="yellow") + for i, suggestion in enumerate(suggestions, 1): + content.append(f" {i}. {suggestion}\n", style="white") + + panel = Panel( + content, + title="[red]Error[/red]", + border_style="red", + expand=False, + ) + + console.print(panel) + + +def display_success_panel( + message: str, + details: Optional[Dict[str, Any]] = None, + console: Optional[Console] = None, +): + """Display a success panel with optional details. + + Args: + message: Success message. + details: Dictionary of additional details to display. + console: Rich console instance. + """ + console = console or Console() + + content = Text(message, style="green") + + if details: + content.append("\n\nDetails:\n", style="blue") + for key, value in details.items(): + formatted_key = key.replace("_", " ").title() + content.append(f" {formatted_key}: {value}\n", style="white") + + panel = Panel( + content, + title="[green]Success[/green]", + border_style="green", + expand=False, + ) + + console.print(panel) diff --git a/policyengine_uk_data/utils/qrf.py b/policyengine_uk_data/utils/qrf.py index 147a8de24..98665889c 100644 --- a/policyengine_uk_data/utils/qrf.py +++ b/policyengine_uk_data/utils/qrf.py @@ -1,74 +1,79 @@ -try: - from quantile_forest import RandomForestQuantileRegressor -except ImportError: - pass +""" +Quantile regression forest wrapper for imputation tasks. + +This module provides a simplified interface to the microimpute QRF model +with serialisation support for trained models. +""" + +import logging +from microimpute.models import QRF as MicroImputeQRF import pandas as pd import numpy as np import pickle +# Silence microimpute INFO level logging and warnings +logging.getLogger("microimpute").setLevel(logging.ERROR) +# Also silence root logger warnings from microimpute +logging.getLogger("root").setLevel(logging.ERROR) + class QRF: - categorical_columns: list = None - encoded_columns: list = None - input_columns: list = None - output_columns: list = None + """ + Quantile regression forest model wrapper. - def __init__(self, seed=0, file_path=None): - self.seed = seed + Provides a simple interface to train and use quantile regression forests + for imputation, with support for saving and loading trained models. + """ - if file_path is not None: + def __init__(self, file_path: str = None): + """ + Initialise QRF model. + + Args: + file_path: Path to saved model file. If None, creates new model. + """ + if file_path is None: + self.model = MicroImputeQRF() + else: with open(file_path, "rb") as f: data = pickle.load(f) - self.seed = data["seed"] - self.categorical_columns = data["categorical_columns"] - self.encoded_columns = data["encoded_columns"] - self.input_columns = data["input_columns"] - self.output_columns = data["output_columns"] - self.qrf = data["qrf"] + self.model = data["model"] + self.input_columns = data["input_columns"] - def fit(self, X, y, **qrf_kwargs): + def fit(self, X, y): + """ + Train the model on input data. + + Args: + X: Feature variables DataFrame. + y: Target variables DataFrame. + """ + train_df = pd.concat([X, y], axis=1) + X_cols = X.columns + y_cols = y.columns + self.model = self.model.fit(train_df, X_cols, y_cols) self.input_columns = X.columns - self.categorical_columns = X.select_dtypes(include=["object"]).columns - X = pd.get_dummies( - X, columns=self.categorical_columns, drop_first=True - ) - self.encoded_columns = X.columns - self.output_columns = y.columns - self.qrf = RandomForestQuantileRegressor( - random_state=self.seed, **qrf_kwargs - ) - self.qrf.fit(X, y) - def predict(self, X, count_samples=10, mean_quantile=0.5): - X = pd.get_dummies( - X, columns=self.categorical_columns, drop_first=True - ) - X = X[self.encoded_columns] - pred = self.qrf.predict( - X, quantiles=list(np.linspace(0, 1, count_samples)) - ) - random_generator = np.random.default_rng(self.seed) - a = mean_quantile / (1 - mean_quantile) - input_quantiles = ( - random_generator.beta(a, 1, size=len(X)) * count_samples - ) - input_quantiles = input_quantiles.astype(int) - if len(pred.shape) == 2: - predictions = pred[np.arange(len(pred)), input_quantiles] - else: - predictions = pred[np.arange(len(pred)), :, input_quantiles] - return pd.DataFrame(predictions, columns=self.output_columns) + def predict(self, X): + """ + Predict using the trained model. + + Args: + X: Feature variables DataFrame. + + Returns: + Predictions at the 0.5 quantile (median). + """ + return self.model.predict(X)[0.5] + + def save(self, file_path: str): + """ + Save trained model to file. - def save(self, path): - with open(path, "wb") as f: + Args: + file_path: Path where model should be saved. + """ + with open(file_path, "wb") as f: pickle.dump( - { - "seed": self.seed, - "categorical_columns": self.categorical_columns, - "encoded_columns": self.encoded_columns, - "input_columns": self.input_columns, - "output_columns": self.output_columns, - "qrf": self.qrf, - }, - f, + {"model": self.model, "input_columns": self.input_columns}, f ) diff --git a/policyengine_uk_data/utils/reweight.py b/policyengine_uk_data/utils/reweight.py deleted file mode 100644 index 2dddc1dcf..000000000 --- a/policyengine_uk_data/utils/reweight.py +++ /dev/null @@ -1,73 +0,0 @@ -import numpy as np -import torch -import os - - -def reweight( - original_weights, - loss_matrix, - targets_array, - dropout_rate=0.05, -): - target_names = np.array(loss_matrix.columns) - loss_matrix = torch.tensor(loss_matrix.values, dtype=torch.float32) - targets_array = torch.tensor(targets_array, dtype=torch.float32) - weights = torch.tensor( - np.log(original_weights), requires_grad=True, dtype=torch.float32 - ) - - # TODO: replace this with a call to the python reweight.py package. - def loss(weights): - # Check for Nans in either the weights or the loss matrix - if torch.isnan(weights).any(): - raise ValueError("Weights contain NaNs") - if torch.isnan(loss_matrix).any(): - raise ValueError("Loss matrix contains NaNs") - estimate = weights @ loss_matrix - if torch.isnan(estimate).any(): - raise ValueError("Estimate contains NaNs") - rel_error = ( - ((estimate - targets_array) + 1) / (targets_array + 1) - ) ** 2 - if torch.isnan(rel_error).any(): - raise ValueError("Relative error contains NaNs") - return rel_error.mean() - - def pct_close(weights, t=0.1): - # Return the percentage of metrics that are within t% of the target - estimate = weights @ loss_matrix - abs_error = torch.abs((estimate - targets_array) / (1 + targets_array)) - return (abs_error < t).sum() / abs_error.numel() - - def dropout_weights(weights, p): - if p == 0: - return weights - # Replace p% of the weights with the mean value of the rest of them - mask = torch.rand_like(weights) < p - mean = weights[~mask].mean() - masked_weights = weights.clone() - masked_weights[mask] = mean - return masked_weights - - optimizer = torch.optim.Adam([weights], lr=1e-1) - from tqdm import trange - - start_loss = None - - iterator = range(32) if os.environ.get("DATA_LITE") else range(2048) - for i in iterator: - optimizer.zero_grad() - weights_ = dropout_weights(weights, dropout_rate) - l = loss(torch.exp(weights_)) - close = pct_close(torch.exp(weights_)) - if start_loss is None: - start_loss = l.item() - loss_rel_change = (l.item() - start_loss) / start_loss - l.backward() - if i % 100 == 0: - print( - f"Loss: {l.item()}, Rel change: {loss_rel_change}, Epoch: {i}, Within 10%: {close:.2%}" - ) - optimizer.step() - - return torch.exp(weights).detach().numpy() diff --git a/policyengine_uk_data/utils/stack.py b/policyengine_uk_data/utils/stack.py new file mode 100644 index 000000000..2ded165cb --- /dev/null +++ b/policyengine_uk_data/utils/stack.py @@ -0,0 +1,24 @@ +from policyengine_uk.data import UKSingleYearDataset +import pandas as pd + + +def stack_datasets( + data_1: UKSingleYearDataset, data_2: UKSingleYearDataset +) -> UKSingleYearDataset: + person_id_offset = data_1.person.person_id.max() + 1 + benunit_id_offset = data_1.benunit.benunit_id.max() + 1 + household_id_offset = data_1.household.household_id.max() + 1 + data_2.person.person_id += person_id_offset + data_2.person.person_benunit_id += benunit_id_offset + data_2.person.person_household_id += household_id_offset + data_2.benunit.benunit_id += benunit_id_offset + data_2.household.household_id += household_id_offset + + return UKSingleYearDataset( + person=pd.concat([data_1.person, data_2.person], ignore_index=True), + benunit=pd.concat([data_1.benunit, data_2.benunit], ignore_index=True), + household=pd.concat( + [data_1.household, data_2.household], ignore_index=True + ), + fiscal_year=data_1.time_period, + ) diff --git a/policyengine_uk_data/utils/subsample.py b/policyengine_uk_data/utils/subsample.py new file mode 100644 index 000000000..5adc467aa --- /dev/null +++ b/policyengine_uk_data/utils/subsample.py @@ -0,0 +1,48 @@ +""" +Dataset subsampling utilities for PolicyEngine UK. + +This module provides functions to create smaller samples from full datasets, +useful for testing and development workflows. +""" + +from policyengine_uk.data import UKSingleYearDataset +import numpy as np + + +def subsample_dataset( + dataset: UKSingleYearDataset, + sample_size: int, + seed: int = 42, +): + """ + Subsample a UKSingleYearDataset to a specified sample size. + + Parameters: + dataset (UKSingleYearDataset): The dataset to subsample. + sample_size (int): The number of samples to retain. + seed (int): Random seed for reproducibility. + + Returns: + UKSingleYearDataset: A new dataset with the specified sample size. + """ + np.random.seed(seed) + household_ids = np.random.choice( + dataset.household.household_id.values, + size=sample_size, + replace=False, + ) + person_filter = dataset.person.person_household_id.isin(household_ids) + benunit_ids = dataset.person.person_benunit_id[ + dataset.person.person_household_id.isin(household_ids) + ] + benunit_filter = dataset.benunit.benunit_id.isin(benunit_ids) + household_filter = dataset.household.household_id.isin(household_ids) + + subsampled_dataset = UKSingleYearDataset( + person=dataset.person[person_filter], + benunit=dataset.benunit[benunit_filter], + household=dataset.household[household_filter], + fiscal_year=dataset.time_period, + ) + + return subsampled_dataset diff --git a/policyengine_uk_data/utils/uprating.py b/policyengine_uk_data/utils/uprating.py index 3b413648d..5ae50894b 100644 --- a/policyengine_uk_data/utils/uprating.py +++ b/policyengine_uk_data/utils/uprating.py @@ -1,5 +1,6 @@ from policyengine_uk_data.storage import STORAGE_FOLDER import pandas as pd +from policyengine_uk.data import UKSingleYearDataset START_YEAR = 2020 END_YEAR = 2034 @@ -56,5 +57,25 @@ def uprate_values(values, variable_name, start_year=2020, end_year=2034): return values * relative_change +def uprate_dataset(dataset: UKSingleYearDataset, target_year=2034): + dataset = dataset.copy() + uprating_factors = pd.read_csv(STORAGE_FOLDER / "uprating_factors.csv") + uprating_factors = uprating_factors.set_index("Variable") + start_year = dataset.time_period + + for table in dataset.tables: + for variable in table.columns: + if variable in uprating_factors.index: + factor = ( + uprating_factors.loc[variable, str(target_year)] + / uprating_factors.loc[variable, str(start_year)] + ) + table[variable] *= factor + + dataset.time_period = target_year + + return dataset + + if __name__ == "__main__": create_policyengine_uprating_factors_table() diff --git a/pyproject.toml b/pyproject.toml index 62dfa860b..cdf59befa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,9 +11,9 @@ authors = [ {name = "PolicyEngine", email = "hello@policyengine.org"}, ] license = {file = "LICENSE"} -requires-python = ">=3.11" +requires-python = ">=3.13" dependencies = [ - "policyengine_core", + "policyengine-core>=3.19.4", "requests", "tqdm", "tabulate", @@ -21,8 +21,11 @@ dependencies = [ "policyengine", "google-cloud-storage", "google-auth", - "uk-public-services-imputation", - "policyengine-uk==2.40.2", + "policyengine-uk>=2.43.5", + "microcalibrate>=0.18.0", + "microimpute>=1.0.1", + "black>=25.1.0", + "rich>=13.0.0", ] [project.optional-dependencies] @@ -47,8 +50,18 @@ include-package-data = true [tool.pytest.ini_options] addopts = "-v" -testpaths = [ - "tests", +testpaths = ["policyengine_uk_data/tests"] +python_files = ["test_*.py", "*_test.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "unit: marks tests as unit tests", + "integration: marks tests as integration tests", +] +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::PendingDeprecationWarning", ] [tool.black] diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..b25e1f02d --- /dev/null +++ b/uv.lock @@ -0,0 +1,2096 @@ +version = 1 +revision = 2 +requires-python = ">=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 = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, +] + +[[package]] +name = "alembic" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/52/72e791b75c6b1efa803e491f7cbab78e963695e76d4ada05385252927e76/alembic-1.16.4.tar.gz", hash = "sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2", size = 1968161, upload-time = "2025-07-10T16:17:20.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/62/96b5217b742805236614f05904541000f55422a6060a90d7fd4ce26c172d/alembic-1.16.4-py3-none-any.whl", hash = "sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d", size = 247026, upload-time = "2025-07-10T16:17:21.845Z" }, +] + +[[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 = "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 = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[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.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, +] + +[[package]] +name = "blosc2" +version = "3.6.1" +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/82/cb/ed9ee34a3835dcdee67927bcdc55ec3e912a9d08500612db05aebb885dd1/blosc2-3.6.1.tar.gz", hash = "sha256:0b6f05311fbee9e9dc23bd7f53a8690af3b60eef640a059f1eb624ca6699cc59", size = 3657993, upload-time = "2025-07-17T16:22:58.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/6a/cb3c693bd13050d9f68e180e9c5f2fa22060c1fcd04164eae4dd6a97c831/blosc2-3.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47c4b5878795a4bd63f1c93c2bf286939a216e740227bcb18708654196972346", size = 4016932, upload-time = "2025-07-17T16:22:51.212Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a8/0ba60e4810af3d9daee1cc7f8b2a5f93da6b76e65e3e195b0a34a576bf06/blosc2-3.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c32b8ec2f878e77476c457cc57af57cb66e87a026850378d16659f543e1db2a", size = 3374697, upload-time = "2025-07-17T16:22:52.923Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2b/6df9bf29d698dab1f6ee63e96bcf689546e6875af3d0431b90ad2b491888/blosc2-3.6.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9fc209348cbbedce1779ea4d7ce91b349e9298bfd32b92c274c3b5eb444dc206", size = 4287893, upload-time = "2025-07-17T16:22:54.345Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a6/6af387f01b3442e5c14f02cd05ce67e0232984cb4f34dab31e6e319c3ad8/blosc2-3.6.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2332c14034a9f9f5739ec976af24f208677fe964fe1a196c9ae7603ba80ed886", size = 4426379, upload-time = "2025-07-17T16:22:55.692Z" }, + { url = "https://files.pythonhosted.org/packages/87/64/34c1e5c3cd4ada2bebc13880715647cab660f8db85a57210dc4932021167/blosc2-3.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:e440a600017592e37747f48592bfbc74baa848a74cf41513adf53287fd213015", size = 2218905, upload-time = "2025-07-17T16:22:57.169Z" }, +] + +[[package]] +name = "build" +version = "1.2.2.post1" +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/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "caugetch" +version = "0.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/ec/519cb37e3e58e23a5b02a74049128f6e701ccd8892b0cebecf701fac6177/caugetch-0.0.1.tar.gz", hash = "sha256:6f6ddb3b928fa272071b02aabb3342941cd99992f27413ba8c189eb4dc3e33b0", size = 2071, upload-time = "2019-10-15T22:39:49.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/33/64fee4626ec943c2d0c4eee31c784dab8452dfe014916190730880d4ea62/caugetch-0.0.1-py3-none-any.whl", hash = "sha256:ee743dcbb513409cd24cfc42435418073683ba2f4bb7ee9f8440088a47d59277", size = 3439, upload-time = "2019-10-15T22:39:47.122Z" }, +] + +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "clipboard" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyperclip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/38/17f3885713d0f39994563029942b1d31c93d4e56d80da505abfbfb3a3bc4/clipboard-0.0.4.tar.gz", hash = "sha256:a72a78e9c9bf68da1c3f29ee022417d13ec9e3824b511559fd2b702b1dd5b817", size = 1713, upload-time = "2014-05-22T12:49:08.683Z" } + +[[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.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624, upload-time = "2024-10-29T18:34:51.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424, upload-time = "2024-10-29T18:34:49.815Z" }, +] + +[[package]] +name = "datetime" +version = "5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/66/e284b9978fede35185e5d18fb3ae855b8f573d8c90a56de5f6d03e8ef99e/DateTime-5.5.tar.gz", hash = "sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3", size = 63671, upload-time = "2024-03-21T07:26:50.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/78/8e382b8cb4346119e2e04270b6eb4a01c5ee70b47a8a0244ecdb157204f7/DateTime-5.5-py3-none-any.whl", hash = "sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120", size = 52649, upload-time = "2024-03-21T07:26:47.849Z" }, +] + +[[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 = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[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.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, +] + +[[package]] +name = "furo" +version = "2025.7.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/d0/69/312cd100fa45ddaea5a588334d2defa331ff427bcb61f5fe2ae61bdc3762/furo-2025.7.19.tar.gz", hash = "sha256:4164b2cafcf4023a59bb3c594e935e2516f6b9d35e9a5ea83d8f6b43808fe91f", size = 1662054, upload-time = "2025-07-19T10:52:09.754Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/34/2b07b72bee02a63241d654f5d8af87a2de977c59638eec41ca356ab915cd/furo-2025.7.19-py3-none-any.whl", hash = "sha256:bdea869822dfd2b494ea84c0973937e35d1575af088b6721a29c7f7878adc9e3", size = 342175, upload-time = "2025-07-19T10:52:02.399Z" }, +] + +[[package]] +name = "getpass4" +version = "0.0.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "caugetch" }, + { name = "clipboard" }, + { name = "colorama" }, + { name = "pyperclip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f9/312f84afc384f693d02eb4ff7306a7268577a8b808aa08f0124c9abba683/getpass4-0.0.14.1.tar.gz", hash = "sha256:80aa4e3a665f2eccc6cda3ee22125eeb5c6338e91c40c4fd010b3c94c7aa4d3a", size = 5078, upload-time = "2021-11-28T17:08:47.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/d3/ea114aba31f76418b2162e811793cde2e822c9d9ea8ca98d67f9e1f1bde6/getpass4-0.0.14.1-py3-none-any.whl", hash = "sha256:6642c11fb99db1bec90b963e863ec71cdb0b8888000f5089c6377bfbf833f8a9", size = 8683, upload-time = "2021-11-28T17:08:45.468Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.25.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/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "3.2.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/52/5b/6d4627484248e018a926dde114c4034656570da9c1c438e3db061fa42de5/google_cloud_storage-3.2.0.tar.gz", hash = "sha256:decca843076036f45633198c125d1861ffbf47ebf5c0e3b98dcb9b2db155896c", size = 7669611, upload-time = "2025-07-07T05:14:06.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/48/823ce62cf29d04db6508971a0db13a72c1c9faf67cea2c206b1c9c9f1f02/google_cloud_storage-3.2.0-py3-none-any.whl", hash = "sha256:ff7a9a49666954a7c3d1598291220c72d3b9e49d9dfcf9dfaecb301fc4fb0b24", size = 176133, upload-time = "2025-07-07T05:14:05.059Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467, upload-time = "2025-03-26T14:36:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309, upload-time = "2025-03-26T15:06:15.318Z" }, + { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133, upload-time = "2025-03-26T14:41:34.388Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773, upload-time = "2025-03-26T14:41:35.19Z" }, + { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475, upload-time = "2025-03-26T14:29:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243, upload-time = "2025-03-26T14:41:35.975Z" }, + { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870, upload-time = "2025-03-26T14:41:37.08Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, + { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, + { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, + { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, + { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, + { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" }, + { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443, upload-time = "2025-06-05T16:48:23.113Z" }, + { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, +] + +[[package]] +name = "h5py" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/57/dfb3c5c3f1bf5f5ef2e59a22dec4ff1f3d7408b55bfcefcfb0ea69ef21c6/h5py-3.14.0.tar.gz", hash = "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", size = 424323, upload-time = "2025-06-06T14:06:15.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", size = 3289245, upload-time = "2025-06-06T14:05:28.24Z" }, + { url = "https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", size = 2807335, upload-time = "2025-06-06T14:05:31.997Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ce/3a21d87896bc7e3e9255e0ad5583ae31ae9e6b4b00e0bcb2a67e2b6acdbc/h5py-3.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", size = 4700675, upload-time = "2025-06-06T14:05:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", size = 4921632, upload-time = "2025-06-06T14:05:43.464Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", size = 2852929, upload-time = "2025-06-06T14:05:47.659Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894, upload-time = "2025-06-20T21:48:28.114Z" }, + { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134, upload-time = "2025-06-20T21:48:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009, upload-time = "2025-06-20T21:48:33.987Z" }, + { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245, upload-time = "2025-06-20T21:48:36.051Z" }, + { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/cd/841bc8e0550d69f632a15cdd70004e95ba92cd0fbe13087d6669e2bb5f44/huggingface_hub-0.34.1.tar.gz", hash = "sha256:6978ed89ef981de3c78b75bab100a214843be1cc9d24f8e9c0dc4971808ef1b1", size = 456783, upload-time = "2025-07-25T14:54:54.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/cf/dd53c0132f50f258b06dd37a4616817b1f1f6a6b38382c06effd04bb6881/huggingface_hub-0.34.1-py3-none-any.whl", hash = "sha256:60d843dcb7bc335145b20e7d2f1dfe93910f6787b2b38a936fb772ce2a83757c", size = 558788, upload-time = "2025-07-25T14:54:52.957Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[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.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[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 = "itables" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/20/ff4b8bc879c4573e91f810ac2c556c565cbedb994237700ae3ffb42e0e9b/itables-2.4.4.tar.gz", hash = "sha256:29e2b771f7fd3ab70b9d2234615b573da325e610832697e986680ccc036aaf5b", size = 2230757, upload-time = "2025-07-08T20:32:06.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/cc/a2af9cb19114be0f0afc0242d3e5cb018ac0cbdcf17816c2dd71b68edc79/itables-2.4.4-py3-none-any.whl", hash = "sha256:eb59b24700f6b4ba2a7810a226eeab207a83a2461120f1fd65220f70872eaa45", size = 2261033, upload-time = "2025-07-08T20:32:04.879Z" }, +] + +[[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.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/29/c0d39be806b5d5c201e9bf5265e43cf0e88bd63fb4e38edfc7a212ca38a7/jellyfish-1.2.0.tar.gz", hash = "sha256:5c7d73db4045dcc53b6efbfea21f3d3da432d3e052dc51827574d1a447fc23b4", size = 364693, upload-time = "2025-03-31T15:43:18.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/4e/2f10011b5a80c56bb0f2775ee7283a3290fb9ec4e67c48c0342671a6d6e0/jellyfish-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8b995bdf97d43cdca1e6bd5375f351bcb85c7f5e8760fe4a28c63eb0e6104075", size = 325372, upload-time = "2025-03-31T15:42:21.563Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/e15034422abf21e28b43155d21f4e34ae7349fad6c682be12c739d79119b/jellyfish-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:559c1d6f17ba51639843b958a0d57ece5c4155e6b820c4acb3f3437437625ef3", size = 322333, upload-time = "2025-03-31T15:42:23.015Z" }, + { url = "https://files.pythonhosted.org/packages/0b/62/cdb56ed6641c5a23bb00c775ea54108423b40d2376bed186455cc39f4a0b/jellyfish-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4439f4066ccc5dd6a7a15cb06941f5150bab646201e9e014a7d34d65cbe89fe", size = 354567, upload-time = "2025-03-31T15:42:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/e0/97/47830ff857307936313e345010cf7626e7b3a233549cfc258c7342553de4/jellyfish-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbf866d2b967fd2d5380134fdcb47d4f113e24d659b46c38e55da80c215d2042", size = 363379, upload-time = "2025-03-31T15:42:25.407Z" }, + { url = "https://files.pythonhosted.org/packages/8e/95/4e3d5bac918aa7b0e1cebff39822add0844dbfc433870949dccc3df8aae3/jellyfish-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9290b82276bba1941ad0f98226f51b43aeef7bdedb927b9266516b4519b9012", size = 355215, upload-time = "2025-03-31T15:42:26.695Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2f/1d7aa3b7dc3d7e6a7f9e832a98440d14641670394c11718f7584dab434b2/jellyfish-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:052345ded2b00104a50acbab35c671efe06f40790202f6a2fc279ad645f31ab2", size = 532669, upload-time = "2025-03-31T15:42:27.806Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0e/d527f9425e9462463e3b0ae748ec39ec5a2ebaa829725ca8c09f8753e364/jellyfish-1.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:465dcf8b17162e3dae2cae0072b22ea9637e6ce8ddd8294181758437cd9c0673", size = 554223, upload-time = "2025-03-31T15:42:29.362Z" }, + { url = "https://files.pythonhosted.org/packages/04/71/533b48054f1ddab7d9b7ad3833a87963200c7aef7ce81e082379da6d1264/jellyfish-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ae5f2e3c5ef14cb5b86afe7ed4078e504f75dd61ca9d9560bef597f9d2237c63", size = 526103, upload-time = "2025-03-31T15:42:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d7/6c5ce80088495b7bb002931d7d0a313143b45fa10e826f11aadd4a97ccdb/jellyfish-1.2.0-cp313-cp313-win32.whl", hash = "sha256:13ee212b6fa294a1b6306693a1553b760d876780e757b9f016010748fe811b4d", size = 212179, upload-time = "2025-03-31T15:42:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/40/aa/332fd282668a353570bdad56d65f526bc28ab73da1a3dd99e670af687186/jellyfish-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8089e918ddb1abae946e92d053f646a7f686d0d051ef69cdfaa28b37352bbdf", size = 216066, upload-time = "2025-03-31T15:42:32.75Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, +] + +[[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 = "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.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[package]] +name = "microcalibrate" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pandas" }, + { name = "torch" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/07/ac9ddc8c56edb44fe4d28d2bca2af466e523bc039fe1fda49e477518e9bc/microcalibrate-0.18.0.tar.gz", hash = "sha256:e660efbecb13d391d87cfe9a938860447a2a4c196ac1efdc8b4fe8e8a58edd26", size = 168087, upload-time = "2025-07-25T14:43:58.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/be/dd3200c8888d9267818426db2c59de60d9aa252e07a93a8e9e55148dd048/microcalibrate-0.18.0-py3-none-any.whl", hash = "sha256:54c9433cb33915d509586c43d63ed9775b55813311676ba4f3efda3f6c7910ae", size = 15744, upload-time = "2025-07-25T14:43:56.58Z" }, +] + +[[package]] +name = "microdf-python" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/25/55c2b0495ae4c3142d61f1283d675494aac4c254e40ecf1ea4b337a051c7/microdf_python-1.0.2.tar.gz", hash = "sha256:5c845974d485598a7002c151f58ec7438e94c04954fc8fdea9238265e7bf02f5", size = 14826, upload-time = "2025-07-24T12:21:08.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1a/aac40a7e58de4133a9cc7630913a8b8e6c76326288b168cbb47f7714c4fd/microdf_python-1.0.2-py3-none-any.whl", hash = "sha256:f7883785e4557d1c8822dbf0d69d7eeab9399f8e67a9bdb716f74554c7580ae7", size = 15823, upload-time = "2025-07-24T12:21:07.356Z" }, +] + +[[package]] +name = "microimpute" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "optuna" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "pydantic" }, + { name = "quantile-forest" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "statsmodels" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/b2/5b5e2936c7a1f65961271e7f63be273cf1d90044cdb1953a9dd6bd90f977/microimpute-1.0.1.tar.gz", hash = "sha256:b42c2159f42f275a8b5765d0287a888623acc0b5a09292a8e740c1bfb4746e68", size = 50277, upload-time = "2025-07-26T16:24:16.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/da/a51038507d1fb40ac04b16be281a83bcb15ccffcc4d75a3a5a39216f7535/microimpute-1.0.1-py3-none-any.whl", hash = "sha256:29624b6502375a7fcfb556c8d16a465c976c945af93043c32e847433e2acdd29", size = 70154, upload-time = "2025-07-26T16:24:15.566Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, +] + +[[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 = "ndindex" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/a0/f584c0b6b998e4981201a1383200663a725f556f439cf58d02a093cb9f91/ndindex-1.10.0.tar.gz", hash = "sha256:20e3a2f0a8ed4646abf0f13296aab0b5b9cc8c5bc182b71b5945e76eb6f558bb", size = 258688, upload-time = "2025-05-21T17:42:22.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ee/8f7aa7dde0f2d947c2e4034f4c58b308bf1f48a18780183e7f84298a573c/ndindex-1.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:50b579a0c57a4072fc97848f1d0db8cb228ca73d050c8bc9d4e7cf2e75510829", size = 161193, upload-time = "2025-05-21T17:41:24.452Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3b/9f2a49b5d3a558e9cd067e0911e1bb8d8d553e1d689bb9a9119c775636b9/ndindex-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0956611e29f51857a54ba0750568ebdbf0eacfad4a262253af2522e77b476369", size = 159952, upload-time = "2025-05-21T17:41:25.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/b9/93273d8dd7a2e155af6ed0bad2f2618202794ffe537184b25ff666cf8e31/ndindex-1.10.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f82aada1f194c5ea11943ca89532cf449881de8c9c2c48b8baa43d467486fdb2", size = 502466, upload-time = "2025-05-21T17:41:27.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/07/c64b0c8416f604f6990da5d1fa97c9de1278a4eec1efcc63b71053b4f0c0/ndindex-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38a56a16edbd62ef039b93e393047e66238d02dbc1e95e95b79c0bdd0a4785f7", size = 526910, upload-time = "2025-05-21T17:41:29.071Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a5/316f13eeda944db14015a6edaebd88fc83b196d86cae9f576be319b93873/ndindex-1.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8b11a3b8fd983adafea988b2a7e51fe8c0be819639b16506a472429069158f6d", size = 1642168, upload-time = "2025-05-21T17:41:31.213Z" }, + { url = "https://files.pythonhosted.org/packages/f3/13/4c1cf1b6280669f32e9960215d6cbed027084b0bb423c924095f247f3185/ndindex-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be7cfaed1e7a72c7e0bbc4a0e1965d3cc8207cb3d56bd351c0cb2b2d94db0bdd", size = 1557347, upload-time = "2025-05-21T17:41:32.893Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/36124ca146aaa6e84ac479e06a81b5ae9ebde2e3b4b2c77c49492bcfebae/ndindex-1.10.0-cp313-cp313-win32.whl", hash = "sha256:f779a0c20ffd617535bf57c7437d5521d5453daf2e0db0d148301df6b24c0932", size = 148623, upload-time = "2025-05-21T17:41:34.628Z" }, + { url = "https://files.pythonhosted.org/packages/23/38/13169cc35be65a6683784c5a1f2c7e6d2219f58fb56abe9d13ef762a634a/ndindex-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:1ef8d71e0ddf0c6e39e64f1e328a37ebefcca1b89218a4068c353851bcb4cb0f", size = 156188, upload-time = "2025-05-21T17:41:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/29/f6/ba98045516f39b0414d03c466e7c46b79290cd54a73ff961b9081bc66a6e/ndindex-1.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6fcefeefc48815dd8e99999999477d91d4287d8034b1c81084042a49976b212c", size = 167198, upload-time = "2025-05-21T17:41:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/14/4c8b1256009cda78387e6e3035d4b86582d98b557e56f7ee8f58df3e57b4/ndindex-1.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:882367d3d5a4d20155c23d890bf01ffbac78019eee09a9456ff3322f62eb34c1", size = 167324, upload-time = "2025-05-21T17:41:39.004Z" }, + { url = "https://files.pythonhosted.org/packages/c5/34/a1e8117c0fe5a862da9e7f0162233340c7a9bbd728161a06cd0ad856514e/ndindex-1.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f04b3eeced5a10f1c00197ee93c913a691467c752306c0d97e6df9c02af4e6d", size = 608219, upload-time = "2025-05-21T17:41:40.556Z" }, + { url = "https://files.pythonhosted.org/packages/19/6c/f9b449d0d9db404637d026798a208b677c04c349ab740db33ab78065603d/ndindex-1.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cb68232e58ca6cc92ddc8cdddcff8dcdfa5de030e89de8457e5d43de77bcc331", size = 1639541, upload-time = "2025-05-21T17:41:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/2c/14/0bfe948a092ddba3c23f18a6f4e3fc2029adfc3e433e634410ba98b7700f/ndindex-1.10.0-cp313-cp313t-win32.whl", hash = "sha256:af8ecd5a0221482e9b467918b90e78f85241572102fdcf0a941ef087e7dcf2e4", size = 157843, upload-time = "2025-05-21T17:41:43.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/0e7d831e918db3e8819f7327e835e4b106fe91ed0c865e96fb952f936b7f/ndindex-1.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2fb32342379547032fd25dbf5bfc7003ebc1bde582779e9a171373a738d6fb8b", size = 166116, upload-time = "2025-05-21T17:41:45.506Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "numexpr" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/8f/2cc977e91adbfbcdb6b49fdb9147e1d1c7566eb2c0c1e737e9a47020b5ca/numexpr-2.11.0.tar.gz", hash = "sha256:75b2c01a4eda2e7c357bc67a3f5c3dd76506c15b5fd4dc42845ef2e182181bad", size = 108960, upload-time = "2025-06-09T11:05:56.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/63/dbf4fb6c48006d413a82db138d03c3c007d0ed0684f693c4b77196448660/numexpr-2.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eb766218abad05c7c3ddad5367d0ec702d6152cb4a48d9fd56a6cef6abade70c", size = 147495, upload-time = "2025-06-09T11:05:25.105Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e4/2fbbf5b9121f54722dc4d4dfc75bc0b4e8ee2675f92ec86ee5697aecc53f/numexpr-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2036be213a6a1b5ce49acf60de99b911a0f9d174aab7679dde1fae315134f826", size = 136839, upload-time = "2025-06-09T11:05:26.171Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3f/aa36415919c90f712a11127eaa7c0c8d045768d62a484a29364e4801c383/numexpr-2.11.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:096ec768bee2ef14ac757b4178e3c5f05e5f1cb6cae83b2eea9b4ba3ec1a86dd", size = 416240, upload-time = "2025-06-09T11:05:27.634Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7d/4911f40d3610fc5557029f0d1f20ef9f571488319567ac4d8ee6d0978ee6/numexpr-2.11.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1719788a787808c15c9bb98b6ff0c97d64a0e59c1a6ebe36d4ae4d7c5c09b95", size = 406641, upload-time = "2025-06-09T11:05:29.408Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/d00e717e77691c410c6c461d7880b4c498896874316acc0e044d7eafacbf/numexpr-2.11.0-cp313-cp313-win32.whl", hash = "sha256:6b5fdfc86cbf5373ea67d554cc6f08863825ea8e928416bed8d5285e387420c6", size = 153313, upload-time = "2025-06-09T11:05:30.633Z" }, + { url = "https://files.pythonhosted.org/packages/52/a2/93346789e6d73a76fdb68171904ade25c112f25df363a8f602c6b21bc220/numexpr-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ff337b36db141a1a0b49f01282783744f49f0d401cc83a512fc5596eb7db5c6", size = 146340, upload-time = "2025-06-09T11:05:31.771Z" }, + { url = "https://files.pythonhosted.org/packages/0b/20/c0e3aaf3cc4497e5253df2523a55c83b9d316cb5c9d5caaa4a1156cef6e3/numexpr-2.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b9854fa70edbe93242b8bb4840e58d1128c45766d9a70710f05b4f67eb0feb6e", size = 148206, upload-time = "2025-06-09T11:05:33.3Z" }, + { url = "https://files.pythonhosted.org/packages/de/49/22fd38ac990ba333f25b771305a5ffcd98c771f4d278868661ffb26deac1/numexpr-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:321736cb98f090ce864b58cc5c37661cb5548e394e0fe24d5f2c7892a89070c3", size = 137573, upload-time = "2025-06-09T11:05:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1e/50074e472e9e6bea4fe430869708d9ede333a187d8d0740e70d5a9560aad/numexpr-2.11.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5cc434eb4a4df2fe442bcc50df114e82ff7aa234657baf873b2c9cf3f851e8e", size = 426674, upload-time = "2025-06-09T11:05:35.553Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/7ccbc72b950653df62d29e2531c811ed80cfff93c927a5bfd86a71edb4da/numexpr-2.11.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:238d19465a272ada3967600fada55e4c6900485aefb42122a78dfcaf2efca65f", size = 416037, upload-time = "2025-06-09T11:05:36.601Z" }, + { url = "https://files.pythonhosted.org/packages/31/7c/bbccad2734dd4b251cc6bdff8cf5ded18b5383f5a05aa8de7bf02acbb65b/numexpr-2.11.0-cp313-cp313t-win32.whl", hash = "sha256:0db4c2dcad09f9594b45fce794f4b903345195a8c216e252de2aa92884fd81a8", size = 153967, upload-time = "2025-06-09T11:05:37.907Z" }, + { url = "https://files.pythonhosted.org/packages/75/d7/41287384e413e8d20457d35e264d9c9754e65eb13a988af51ceb7057f61b/numexpr-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a69b5c02014448a412012752dc46091902d28932c3be0c6e02e73cecceffb700", size = 147207, upload-time = "2025-06-09T11:05:39.011Z" }, +] + +[[package]] +name = "numpy" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090, upload-time = "2024-11-02T17:48:55.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263, upload-time = "2024-11-02T17:40:39.528Z" }, + { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771, upload-time = "2024-11-02T17:41:01.368Z" }, + { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805, upload-time = "2024-11-02T17:41:11.213Z" }, + { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380, upload-time = "2024-11-02T17:41:22.19Z" }, + { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451, upload-time = "2024-11-02T17:41:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822, upload-time = "2024-11-02T17:42:07.595Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822, upload-time = "2024-11-02T17:42:32.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598, upload-time = "2024-11-02T17:42:53.773Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021, upload-time = "2024-11-02T17:46:19.171Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405, upload-time = "2024-11-02T17:46:38.177Z" }, + { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062, upload-time = "2024-11-02T17:43:24.599Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839, upload-time = "2024-11-02T17:43:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031, upload-time = "2024-11-02T17:43:54.585Z" }, + { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977, upload-time = "2024-11-02T17:44:05.31Z" }, + { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951, upload-time = "2024-11-02T17:44:25.881Z" }, + { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655, upload-time = "2024-11-02T17:44:50.115Z" }, + { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902, upload-time = "2024-11-02T17:45:15.685Z" }, + { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180, upload-time = "2024-11-02T17:45:37.234Z" }, + { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907, upload-time = "2024-11-02T17:45:48.951Z" }, + { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098, upload-time = "2024-11-02T17:46:07.941Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.6.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/eb/ff4b8c503fa1f1796679dce648854d58751982426e4e4b37d6fce49d259c/nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb", size = 393138322, upload-time = "2024-11-20T17:40:25.65Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.6.80" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/60/7b6497946d74bcf1de852a21824d63baad12cd417db4195fc1bfe59db953/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132", size = 8917980, upload-time = "2024-11-20T17:36:04.019Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/120ee57b218d9952c379d1e026c4479c9ece9997a4fb46303611ee48f038/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73", size = 8917972, upload-time = "2024-10-01T16:58:06.036Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2e/46030320b5a80661e88039f59060d1790298b4718944a65a7f2aeda3d9e9/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53", size = 23650380, upload-time = "2024-10-01T17:00:14.643Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/23/e717c5ac26d26cf39a27fbc076240fad2e3b817e5889d671b67f4f9f49c5/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7", size = 897690, upload-time = "2024-11-20T17:35:30.697Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/65c05e161eeddbafeca24dc461f47de550d9fa8a7e04eb213e32b55cfd99/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8", size = 897678, upload-time = "2024-10-01T16:57:33.821Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.5.1.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/78/4535c9c7f859a64781e43c969a3a7e84c54634e319a996d43ef32ce46f83/nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2", size = 570988386, upload-time = "2024-10-25T19:54:26.39Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/16/73727675941ab8e6ffd86ca3a4b7b47065edcca7a997920b831f8147c99d/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5", size = 200221632, upload-time = "2024-11-20T17:41:32.357Z" }, + { url = "https://files.pythonhosted.org/packages/60/de/99ec247a07ea40c969d904fc14f3a356b3e2a704121675b75c366b694ee1/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca", size = 200221622, upload-time = "2024-10-01T17:03:58.79Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.11.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/66/cc9876340ac68ae71b15c743ddb13f8b30d5244af344ec8322b449e35426/nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159", size = 1142103, upload-time = "2024-11-20T17:42:11.83Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.7.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/1b/44a01c4e70933637c93e6e1a8063d1e998b50213a6b65ac5a9169c47e98e/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf", size = 56279010, upload-time = "2024-11-20T17:42:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/4a/aa/2c7ff0b5ee02eaef890c0ce7d4f74bc30901871c5e45dee1ae6d0083cd80/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117", size = 56279000, upload-time = "2024-10-01T17:04:45.274Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.1.2" +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/f0/6e/c2cf12c9ff8b872e92b4a5740701e51ff17689c4d726fca91875b07f655d/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c", size = 158229790, upload-time = "2024-11-20T17:43:43.211Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/baba53585da791d043c10084cf9553e074548408e04ae884cfe9193bd484/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6", size = 158229780, upload-time = "2024-10-01T17:05:39.875Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/1e/b8b7c2f4099a37b96af5c9bb158632ea9e5d9d27d7391d7eb8fc45236674/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73", size = 216561367, upload-time = "2024-11-20T17:44:54.824Z" }, + { url = "https://files.pythonhosted.org/packages/43/ac/64c4316ba163e8217a99680c7605f779accffc6a4bcd0c778c12948d3707/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f", size = 216561357, upload-time = "2024-10-01T17:06:29.861Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/9a/72ef35b399b0e183bc2e8f6f558036922d453c4d8237dab26c666a04244b/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46", size = 156785796, upload-time = "2024-10-15T21:29:17.709Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.26.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/ca/f42388aed0fddd64ade7493dbba36e1f534d4e6fdbdd355c6a90030ae028/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6", size = 201319755, upload-time = "2025-03-13T00:29:55.296Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/d7/c5383e47c7e9bf1c99d5bd2a8c935af2b6d705ad831a7ec5c97db4d82f4f/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a", size = 19744971, upload-time = "2024-11-20T17:46:53.366Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/9a/fff8376f8e3d084cd1530e1ef7b879bb7d6d265620c95c1b322725c694f4/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2", size = 89276, upload-time = "2024-11-20T17:38:27.621Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265, upload-time = "2024-10-01T17:00:38.172Z" }, +] + +[[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.4.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/a5/e0/b303190ae8032d12f320a24c42af04038bacb1f3b17ede354dd1044a5642/optuna-4.4.0.tar.gz", hash = "sha256:a9029f6a92a1d6c8494a94e45abd8057823b535c2570819072dbcdc06f1c1da4", size = 467708, upload-time = "2025-06-16T05:13:00.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/5e/068798a8c7087863e7772e9363a880ab13fe55a5a7ede8ec42fab8a1acbb/optuna-4.4.0-py3-none-any.whl", hash = "sha256:fad8d9c5d5af993ae1280d6ce140aecc031c514a44c3b639d8c8658a8b7920ea", size = 395949, upload-time = "2025-06-16T05:12:58.37Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" }, + { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" }, + { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" }, + { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" }, + { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010, upload-time = "2024-11-12T14:10:54.642Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923, upload-time = "2024-11-12T14:10:52.85Z" }, +] + +[[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.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/de/241caa0ca606f2ec5fe0c1f4261b0465df78d786a38da693864a116c37f4/pip-25.1.1.tar.gz", hash = "sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077", size = 1940155, upload-time = "2025-05-02T15:14:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl", hash = "sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af", size = 1825227, upload-time = "2025-05-02T15:13:59.102Z" }, +] + +[[package]] +name = "pip-system-certs" +version = "5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/0c/a338ae5d49192861cf54da4d5c2af0efe47edbaa0827995b284005366ca5/pip_system_certs-5.2.tar.gz", hash = "sha256:80b776b5cf17191bf99d313699b7fce2fdb84eb7bbb225fd134109a82706406f", size = 5408, upload-time = "2025-06-17T23:33:15.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/ce/608bbe82759363d6e752dd370daf066be3be8e7ffdb79838501ed6104173/pip_system_certs-5.2-py3-none-any.whl", hash = "sha256:e6ef3e106d4d02313e33955c2bcc4c2b143b2da07ef91e28a6805a0c1c512126", size = 5866, upload-time = "2025-06-17T23:33:14.554Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[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 = "policyengine" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "diskcache" }, + { name = "getpass4" }, + { name = "google-cloud-storage" }, + { name = "microdf-python" }, + { name = "policyengine-core" }, + { name = "policyengine-uk" }, + { name = "policyengine-us" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/17/7bf9a4ca82e01ab6dcb94cbfe516789860a8d20ba05fec5c32d54837c72d/policyengine-0.6.0.tar.gz", hash = "sha256:fd0d724c30e3b76f1f08fd4f47faaf2e5f797c1475b8dad247ca112b2f342da3", size = 205170, upload-time = "2025-07-17T10:29:19.65Z" } + +[[package]] +name = "policyengine-core" +version = "3.19.4" +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/52/48/9cc189920e7d3f86f857ff098d9447e5625f3639ac31d19b50a23723be70/policyengine_core-3.19.4.tar.gz", hash = "sha256:1e482634ac37186aaa4e1ddff7f097488836f711c399d4dfafe4e7a7ac24776b", size = 159580, upload-time = "2025-07-28T09:18:43.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/ff/879621afd620388027e134ed5461c49a72cf85c519ab0455d736857975c5/policyengine_core-3.19.4-py3-none-any.whl", hash = "sha256:885c3b54de208f5a35518229a18caf0719bc5c39ffa2ac30ee7c953bb621d8a4", size = 220766, upload-time = "2025-07-28T09:18:41.848Z" }, +] + +[[package]] +name = "policyengine-uk" +version = "2.44.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microdf-python" }, + { name = "policyengine-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/d2/32ad42681945b5b8cb2aaa7d9b1e583d5cd1389ea1e1a07a4ce0d1b09827/policyengine_uk-2.44.1.tar.gz", hash = "sha256:c5839efe3c05a030c5f8d12b159c6fb9837e82ed4b00edcb211dd1e8329c7736", size = 1032242, upload-time = "2025-07-29T15:41:44.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/09/280acc297d0a39ced115b9e4a218f2adf551855ee4a0741b16992d0fb1de/policyengine_uk-2.44.1-py3-none-any.whl", hash = "sha256:258c1e97b6f80adc06e68a727b093443f05d4e7fe2ac4ce565a2eed3289b4dda", size = 1585985, upload-time = "2025-07-29T15:41:42.519Z" }, +] + +[[package]] +name = "policyengine-uk-data" +version = "1.17.4" +source = { editable = "." } +dependencies = [ + { name = "black" }, + { name = "google-auth" }, + { name = "google-cloud-storage" }, + { name = "huggingface-hub" }, + { name = "microcalibrate" }, + { name = "microimpute" }, + { name = "policyengine" }, + { name = "policyengine-core" }, + { name = "policyengine-uk" }, + { name = "requests" }, + { name = "tabulate" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "build" }, + { name = "furo" }, + { name = "itables" }, + { name = "pytest" }, + { name = "quantile-forest" }, + { name = "tables" }, + { name = "torch" }, + { name = "yaml-changelog" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", specifier = ">=25.1.0" }, + { name = "black", marker = "extra == 'dev'" }, + { name = "build", marker = "extra == 'dev'" }, + { name = "furo", marker = "extra == 'dev'" }, + { name = "google-auth" }, + { name = "google-cloud-storage" }, + { name = "huggingface-hub" }, + { name = "itables", marker = "extra == 'dev'" }, + { name = "microcalibrate", specifier = ">=0.18.0" }, + { name = "microimpute", specifier = ">=1.0.1" }, + { name = "policyengine" }, + { name = "policyengine-core", specifier = ">=3.19.4" }, + { name = "policyengine-uk", specifier = ">=2.43.5" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "quantile-forest", marker = "extra == 'dev'" }, + { name = "requests" }, + { name = "tables", marker = "extra == 'dev'" }, + { name = "tabulate" }, + { name = "torch", marker = "extra == 'dev'" }, + { name = "tqdm" }, + { name = "yaml-changelog", marker = "extra == 'dev'", specifier = ">=0.1.7" }, +] +provides-extras = ["dev"] + +[[package]] +name = "policyengine-us" +version = "1.351.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microdf-python" }, + { name = "policyengine-core" }, + { name = "policyengine-us-data" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/90/9442d34dd24cbad45b94f94ae65d5bf400964cba1e2e51e19fc0987daac0/policyengine_us-1.351.2.tar.gz", hash = "sha256:3b2b6cd0bdf97ea190514729f4a856a73bb77f3485bd15e93a99eedf70d59a1b", size = 7881079, upload-time = "2025-07-25T16:58:21.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/96/4b7848221f09aaab2af32a2030a67cc95d1e5ea227bab9dc650ac73e75ca/policyengine_us-1.351.2-py3-none-any.whl", hash = "sha256:e0df878a1521820044dfdbb2f0ee600dbf24d731078f40771a593b713802a4f1", size = 5732251, upload-time = "2025-07-25T16:58:16.936Z" }, +] + +[[package]] +name = "policyengine-us-data" +version = "1.41.2" +source = { registry = "https://pypi.org/simple" } +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 = "statsmodels" }, + { name = "tables" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "us" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/d8/087dd032a488ecb52a8ed010091fe30f52b4836137b03d26d0a22e9d16c0/policyengine_us_data-1.41.2.tar.gz", hash = "sha256:c49b5f4fe00a007e50f9d37f30ddfa2d10ecad015c24923170e5da4e1872f4e7", size = 1969838, upload-time = "2025-07-26T21:18:10.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/9c/c152527c8852c493c0deb19a47fbf2151cad1e99ac3749abcb44f9591be6/policyengine_us_data-1.41.2-py3-none-any.whl", hash = "sha256:766d00e89906827b1e6d3d0998b12f7dbcad47f752375b863e29c62f672016bd", size = 1274286, upload-time = "2025-07-26T21:18:08.431Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, +] + +[[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 = "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 = "pydantic" +version = "2.11.7" +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/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[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 = "pyperclip" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961, upload-time = "2024-06-18T20:38:48.401Z" } + +[[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.1" +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/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[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 = "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 = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "quantile-forest" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/42/ae2c90ce40543ea9d34ad0774333dab4a296721c33612d241b864debe8d2/quantile_forest-1.4.0.tar.gz", hash = "sha256:1d0edf8b2f1c4b7d11c940cf1e5740a5381e1d250e5db0feea82d9282c52dab5", size = 98782, upload-time = "2025-01-21T09:22:16.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/cd/d0b4e737287619661c22ce0714e2a9d5d485e571d05654578dc5ba864dc1/quantile_forest-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:22d7902773d023d3f2a3c893fc6cebe0099b5a80ad211f4e0ab2949232da2920", size = 544467, upload-time = "2025-01-21T09:22:00.128Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c7/f3eda7a063e69019db1ae9c60a34a139ad1cfbe0bbbdb3832ea01f2caf10/quantile_forest-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a626c9199e95e2a7d4ba6cf0bf3725e4b567445e651288d4c0467a8af9c53f35", size = 315960, upload-time = "2025-01-21T09:22:01.259Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4a/376791fbf274e1b39afd5ae692a635f477dc565b80a67f66169c146eedfd/quantile_forest-1.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ade4e354464ea46e1292c1761896fd135442df4a62380fb4df2a7fb62eb991b", size = 295666, upload-time = "2025-01-21T09:22:02.368Z" }, + { url = "https://files.pythonhosted.org/packages/22/52/437f422902c051ce2e2ada3c5a81b7a74c6331541982fd5a55c262f1e339/quantile_forest-1.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9d5999e47c543063403b2ee559281b7ef0099114fc545ce6320bfbbff589bc", size = 1870165, upload-time = "2025-01-21T09:22:03.563Z" }, + { url = "https://files.pythonhosted.org/packages/7e/36/10565d99ae90619e9edbdda09ed340f5a82f27f6343ba594814c41095890/quantile_forest-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:fb636b17674969638d09938be0ec72138239cb1d2065f7467333c32ab1214ce0", size = 279935, upload-time = "2025-01-21T09:22:05.553Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[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 = "scikit-learn" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, + { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, + { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, + { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/18/b06a83f0c5ee8cddbde5e3f3d0bb9b702abfa5136ef6d4620ff67df7eee5/scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62", size = 30581216, upload-time = "2025-06-22T16:27:55.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/95/0746417bc24be0c2a7b7563946d61f670a3b491b76adede420e9d173841f/scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451", size = 36418162, upload-time = "2025-06-22T16:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/19/5a/914355a74481b8e4bbccf67259bbde171348a3f160b67b4945fbc5f5c1e5/scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e", size = 28465985, upload-time = "2025-06-22T16:20:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/46/63477fc1246063855969cbefdcee8c648ba4b17f67370bd542ba56368d0b/scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974", size = 20737961, upload-time = "2025-06-22T16:20:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/0fbb5588b73555e40f9d3d6dde24ee6fac7d8e301a27f6f0cab9d8f66ff2/scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc", size = 23377941, upload-time = "2025-06-22T16:20:10.668Z" }, + { url = "https://files.pythonhosted.org/packages/ca/80/a561f2bf4c2da89fa631b3cbf31d120e21ea95db71fd9ec00cb0247c7a93/scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351", size = 33196703, upload-time = "2025-06-22T16:20:16.097Z" }, + { url = "https://files.pythonhosted.org/packages/11/6b/3443abcd0707d52e48eb315e33cc669a95e29fc102229919646f5a501171/scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32", size = 35083410, upload-time = "2025-06-22T16:20:21.734Z" }, + { url = "https://files.pythonhosted.org/packages/20/ab/eb0fc00e1e48961f1bd69b7ad7e7266896fe5bad4ead91b5fc6b3561bba4/scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b", size = 35387829, upload-time = "2025-06-22T16:20:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/57/9e/d6fc64e41fad5d481c029ee5a49eefc17f0b8071d636a02ceee44d4a0de2/scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358", size = 37841356, upload-time = "2025-06-22T16:20:35.112Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a7/4c94bbe91f12126b8bf6709b2471900577b7373a4fd1f431f28ba6f81115/scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe", size = 38403710, upload-time = "2025-06-22T16:21:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/47/20/965da8497f6226e8fa90ad3447b82ed0e28d942532e92dd8b91b43f100d4/scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47", size = 36813833, upload-time = "2025-06-22T16:20:43.925Z" }, + { url = "https://files.pythonhosted.org/packages/28/f4/197580c3dac2d234e948806e164601c2df6f0078ed9f5ad4a62685b7c331/scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08", size = 28974431, upload-time = "2025-06-22T16:20:51.302Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fc/e18b8550048d9224426e76906694c60028dbdb65d28b1372b5503914b89d/scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176", size = 21246454, upload-time = "2025-06-22T16:20:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/8c/48/07b97d167e0d6a324bfd7484cd0c209cc27338b67e5deadae578cf48e809/scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed", size = 23772979, upload-time = "2025-06-22T16:21:03.363Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4f/9efbd3f70baf9582edf271db3002b7882c875ddd37dc97f0f675ad68679f/scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938", size = 33341972, upload-time = "2025-06-22T16:21:11.14Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/9e496a3c5dbe24e76ee24525155ab7f659c20180bab058ef2c5fa7d9119c/scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1", size = 35185476, upload-time = "2025-06-22T16:21:19.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b3/21001cff985a122ba434c33f2c9d7d1dc3b669827e94f4fc4e1fe8b9dfd8/scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706", size = 35570990, upload-time = "2025-06-22T16:21:27.797Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/7ba42647d6709251cdf97043d0c107e0317e152fa2f76873b656b509ff55/scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e", size = 37950262, upload-time = "2025-06-22T16:21:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c4/231cac7a8385394ebbbb4f1ca662203e9d8c332825ab4f36ffc3ead09a42/scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db", size = 38515076, upload-time = "2025-06-22T16:21:45.694Z" }, +] + +[[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 = "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.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +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 = "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/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" }, +] + +[[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 = "sqlalchemy" +version = "2.0.41" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, +] + +[[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.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/cc/8c1bf59bf8203dea1bf2ea811cfe667d7bcc6909c83d8afb02b08e30f50b/statsmodels-0.14.5.tar.gz", hash = "sha256:de260e58cccfd2ceddf835b55a357233d6ca853a1aa4f90f7553a52cc71c6ddf", size = 20525016, upload-time = "2025-07-07T12:14:23.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/fd/4c374108cf108b3130240a5b45847a61f70ddf973429044a81a05189b046/statsmodels-0.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:906263134dd1a640e55ecb01fda4a9be7b9e08558dba9e4c4943a486fdb0c9c8", size = 10013958, upload-time = "2025-07-07T14:35:01.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/36/bf3d7f0e36acd3ba9ec0babd79ace25506b6872780cbd710fb7cd31f0fa2/statsmodels-0.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9118f76344f77cffbb3a9cbcff8682b325be5eed54a4b3253e09da77a74263d3", size = 9674243, upload-time = "2025-07-07T12:08:22.571Z" }, + { url = "https://files.pythonhosted.org/packages/90/ce/a55a6f37b5277683ceccd965a5828b24672bbc427db6b3969ae0b0fc29fb/statsmodels-0.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dc4ee159070557c9a6c000625d85f653de437772fe7086857cff68f501afe45", size = 10219521, upload-time = "2025-07-07T14:23:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/1e/48/973da1ee8bc0743519759e74c3615b39acdc3faf00e0a0710f8c856d8c9d/statsmodels-0.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a085d47c8ef5387279a991633883d0e700de2b0acc812d7032d165888627bef", size = 10453538, upload-time = "2025-07-07T14:24:06.959Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d6/18903fb707afd31cf1edaec5201964dbdacb2bfae9a22558274647a7c88f/statsmodels-0.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f866b2ebb2904b47c342d00def83c526ef2eb1df6a9a3c94ba5fe63d0005aec", size = 10681584, upload-time = "2025-07-07T14:24:21.038Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/80df1bbbfcdc50bff4152f43274420fa9856d56e234d160d6206eb1f5827/statsmodels-0.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:2a06bca03b7a492f88c8106103ab75f1a5ced25de90103a89f3a287518017939", size = 9604641, upload-time = "2025-07-07T12:08:36.23Z" }, +] + +[[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/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 = "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 = "torch" +version = "2.7.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-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/66/81/e48c9edb655ee8eb8c2a6026abdb6f8d2146abd1f150979ede807bb75dcb/torch-2.7.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:03563603d931e70722dce0e11999d53aa80a375a3d78e6b39b9f6805ea0a8d28", size = 98946649, upload-time = "2025-06-04T17:38:43.031Z" }, + { url = "https://files.pythonhosted.org/packages/3a/24/efe2f520d75274fc06b695c616415a1e8a1021d87a13c68ff9dce733d088/torch-2.7.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d632f5417b6980f61404a125b999ca6ebd0b8b4bbdbb5fbbba44374ab619a412", size = 821033192, upload-time = "2025-06-04T17:38:09.146Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d9/9c24d230333ff4e9b6807274f6f8d52a864210b52ec794c5def7925f4495/torch-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:23660443e13995ee93e3d844786701ea4ca69f337027b05182f5ba053ce43b38", size = 216055668, upload-time = "2025-06-04T17:38:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/e086ee36ddcef9299f6e708d3b6c8487c1651787bb9ee2939eb2a7f74911/torch-2.7.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0da4f4dba9f65d0d203794e619fe7ca3247a55ffdcbd17ae8fb83c8b2dc9b585", size = 68925988, upload-time = "2025-06-04T17:38:29.273Z" }, + { url = "https://files.pythonhosted.org/packages/69/6a/67090dcfe1cf9048448b31555af6efb149f7afa0a310a366adbdada32105/torch-2.7.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e08d7e6f21a617fe38eeb46dd2213ded43f27c072e9165dc27300c9ef9570934", size = 99028857, upload-time = "2025-06-04T17:37:50.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/1c/48b988870823d1cc381f15ec4e70ed3d65e043f43f919329b0045ae83529/torch-2.7.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:30207f672328a42df4f2174b8f426f354b2baa0b7cca3a0adb3d6ab5daf00dc8", size = 821098066, upload-time = "2025-06-04T17:37:33.939Z" }, + { url = "https://files.pythonhosted.org/packages/7b/eb/10050d61c9d5140c5dc04a89ed3257ef1a6b93e49dd91b95363d757071e0/torch-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:79042feca1c634aaf6603fe6feea8c6b30dfa140a6bbc0b973e2260c7e79a22e", size = 216336310, upload-time = "2025-06-04T17:36:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/beb45cdf5c4fc3ebe282bf5eafc8dfd925ead7299b3c97491900fe5ed844/torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946", size = 68645708, upload-time = "2025-06-04T17:34:39.852Z" }, +] + +[[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.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/1f/dfb531f90a2d367d914adfee771babbd3f1a5b26c3f5fbc458dee21daa78/triton-3.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b89d846b5a4198317fec27a5d3a609ea96b6d557ff44b56c23176546023c4240", size = 155673035, upload-time = "2025-05-29T23:40:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/28/71/bd20ffcb7a64c753dc2463489a61bf69d531f308e390ad06390268c4ea04/triton-3.3.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3198adb9d78b77818a5388bff89fa72ff36f9da0bc689db2f0a651a67ce6a42", size = 155735832, upload-time = "2025-05-29T23:40:10.522Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[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.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[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 = "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 = "7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3b/e309d731712c1a1866d61b5356a069dd44e5b01e394b6cb49848fa2efbff/zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98", size = 208961, upload-time = "2024-11-28T08:48:29.865Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/78e7cebca6be07c8fc4032bfbb123e500d60efdf7b86727bb8a071992108/zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d", size = 209356, upload-time = "2024-11-28T08:48:33.297Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/627384b745310d082d29e3695db5f5a9188186676912c14b61a78bbc6afe/zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c", size = 264196, upload-time = "2024-11-28T09:18:17.584Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/54548df6dc73e30ac6c8a7ff1da73ac9007ba38f866397091d5a82237bd3/zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398", size = 259237, upload-time = "2024-11-28T08:48:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" }, +]