From c88ba8575912039ed1f5918d5271e90bb949cde9 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Sat, 27 Dec 2025 00:33:24 -0500 Subject: [PATCH 1/2] Add flat file microsimulation notebook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a reusable notebook for running PolicyEngine tax-benefit microsimulations on user-provided CSV datasets. Supports three input formats (tax_unit, person, household) and auto-detects variable entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- run_microsim.ipynb | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 run_microsim.ipynb diff --git a/run_microsim.ipynb b/run_microsim.ipynb new file mode 100644 index 0000000..2df5f55 --- /dev/null +++ b/run_microsim.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": "# PolicyEngine Microsimulation from Flat Files\n\nRun PolicyEngine tax-benefit microsimulations on your own CSV datasets.\n\n## How It Works\n\n1. You provide a CSV with household/tax unit data\n2. This notebook expands it to PolicyEngine's entity structure\n3. PolicyEngine calculates taxes, credits, and benefits\n4. Results are returned as a DataFrame (one row per tax unit)\n\n## Supported Input Formats\n\n| Format | Use When |\n|--------|----------|\n| `tax_unit` | One row per tax filing unit (most common) |\n| `person` | Need different income for each person |\n| `household` | Multiple tax units share a household |\n\n## Quick Start\n\n1. Run all cells in the **Setup** section\n2. Prepare your CSV (see **Input Format Reference** below)\n3. Call `run_microsim()` with your file path\n\n## Finding Variable Names\n\n- **Input variables:** [policyengine.org/us/api/variables](https://policyengine.org/us/api/variables)\n- **Common inputs:** `employment_income`, `self_employment_income`, `social_security`\n- **Common outputs:** `income_tax`, `state_income_tax`, `eitc`, `ctc`" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Setup\n", + "\n", + "Run all cells in this section first." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies if needed (uncomment if running in Colab or fresh environment)\n", + "# !pip install policyengine-us pandas numpy tqdm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Core dependencies\nimport pandas as pd\nimport numpy as np\nimport tempfile\nfrom pathlib import Path\nfrom typing import Dict, Set\nfrom tqdm.auto import tqdm\n\n# PolicyEngine\nfrom policyengine_us import Microsimulation\nfrom policyengine_core.data import Dataset\n\nprint(\"Imports successful!\")" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# =============================================================================\n# Constants and Utilities\n# =============================================================================\n\n# Filing status: map string names to PolicyEngine's integer codes\n# PolicyEngine uses: 1=SINGLE, 2=JOINT, 3=SEPARATE, 4=HEAD_OF_HOUSEHOLD, 5=WIDOW\nFILING_STATUS_MAP = {\n \"SINGLE\": 1, \"JOINT\": 2, \"SEPARATE\": 3, \"HEAD_OF_HOUSEHOLD\": 4, \"WIDOW\": 5,\n 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, # Also accept integers directly\n}\n\n# State codes to FIPS (PolicyEngine uses FIPS codes internally)\nSTATE_CODE_TO_FIPS = {\n 'AL': 1, 'AK': 2, 'AZ': 4, 'AR': 5, 'CA': 6, 'CO': 8, 'CT': 9, 'DE': 10,\n 'DC': 11, 'FL': 12, 'GA': 13, 'HI': 15, 'ID': 16, 'IL': 17, 'IN': 18,\n 'IA': 19, 'KS': 20, 'KY': 21, 'LA': 22, 'ME': 23, 'MD': 24, 'MA': 25,\n 'MI': 26, 'MN': 27, 'MS': 28, 'MO': 29, 'MT': 30, 'NE': 31, 'NV': 32,\n 'NH': 33, 'NJ': 34, 'NM': 35, 'NY': 36, 'NC': 37, 'ND': 38, 'OH': 39,\n 'OK': 40, 'OR': 41, 'PA': 42, 'RI': 44, 'SC': 45, 'SD': 46, 'TN': 47,\n 'TX': 48, 'UT': 49, 'VT': 50, 'VA': 51, 'WA': 53, 'WV': 54, 'WI': 55, 'WY': 56\n}\n\n# Get PolicyEngine's tax-benefit system for auto-detecting variable entities\nfrom policyengine_us import Simulation as _Sim\n_TAX_BENEFIT_SYSTEM = _Sim.default_tax_benefit_system()\n\n\ndef get_variable_entity(var_name: str) -> str:\n \"\"\"\n Get the entity type for a PolicyEngine variable.\n \n PolicyEngine variables belong to different entities:\n - person: individual-level (employment_income, age, etc.)\n - tax_unit: filing unit level (tax credits, filing status, etc.)\n - household: household level (in_nyc, state_fips, etc.)\n - family, spm_unit, marital_unit: other group entities\n \n Returns entity key or \"person\" if variable not found.\n \"\"\"\n var = _TAX_BENEFIT_SYSTEM.variables.get(var_name)\n if var:\n return var.entity.key\n return \"person\" # Default to person if unknown" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# =============================================================================\n# Input Parsers\n# =============================================================================\n\n\ndef parse_person_format(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"Parse person-level input (one row per person).\"\"\"\n required = [\"person_id\", \"household_id\", \"tax_unit_id\", \"age\", \"year\",\n \"is_tax_unit_head\", \"is_tax_unit_spouse\", \"is_tax_unit_dependent\"]\n missing = [c for c in required if c not in df.columns]\n if missing:\n raise ValueError(f\"Missing required columns: {missing}\")\n \n result = df.copy()\n if \"state_code\" in result.columns:\n result[\"state_code\"] = result[\"state_code\"].str.upper()\n else:\n result[\"state_code\"] = \"CA\"\n for col in [\"is_tax_unit_head\", \"is_tax_unit_spouse\", \"is_tax_unit_dependent\"]:\n result[col] = result[col].astype(bool)\n return result\n\n\ndef parse_tax_unit_format(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"\n Parse tax-unit-level input (one row per tax unit).\n Only tax_unit_id and year are required. Expands to person rows.\n \"\"\"\n required = [\"tax_unit_id\", \"year\"]\n missing = [c for c in required if c not in df.columns]\n if missing:\n raise ValueError(f\"Missing required columns: {missing}\")\n \n structural = {\"tax_unit_id\", \"household_id\", \"year\", \"state_code\",\n \"filing_status\", \"age_head\", \"age_spouse\", \"num_dependents\", \"dependent_ages\"}\n pe_vars = [c for c in df.columns if c not in structural]\n \n household_vars = {v for v in pe_vars if get_variable_entity(v) == \"household\"}\n tax_unit_vars = {v for v in pe_vars if get_variable_entity(v) == \"tax_unit\"}\n person_vars = {v for v in pe_vars if v not in household_vars and v not in tax_unit_vars}\n \n persons = []\n for idx, row in df.iterrows():\n tax_unit_id = row[\"tax_unit_id\"]\n household_id = row.get(\"household_id\", tax_unit_id)\n year = int(row[\"year\"])\n state_code = str(row[\"state_code\"]).upper() if \"state_code\" in row and pd.notna(row.get(\"state_code\")) else \"CA\"\n \n # Filing status determines if spouse is created (JOINT=2)\n filing_status_val = row.get(\"filing_status\")\n if pd.isna(filing_status_val):\n filing_status = 1 # SINGLE\n elif isinstance(filing_status_val, str):\n filing_status = FILING_STATUS_MAP.get(filing_status_val.upper(), 1)\n else:\n filing_status = int(filing_status_val)\n \n # Age - use provided or let PE use its default (40)\n age_head = int(row[\"age_head\"]) if \"age_head\" in row and pd.notna(row.get(\"age_head\")) else 40\n \n # Dependents\n num_deps = int(row[\"num_dependents\"]) if \"num_dependents\" in row and pd.notna(row.get(\"num_dependents\")) else 0\n dep_ages_str = row.get(\"dependent_ages\")\n if pd.notna(dep_ages_str) and str(dep_ages_str).strip():\n dep_ages = [int(a.strip()) for a in str(dep_ages_str).split(\",\") if a.strip().isdigit()]\n else:\n dep_ages = [10] * num_deps\n \n pid = idx * 100\n \n # Head\n head = {\n \"person_id\": pid, \"household_id\": household_id, \"tax_unit_id\": tax_unit_id,\n \"year\": year, \"state_code\": state_code, \"age\": age_head,\n \"is_tax_unit_head\": True, \"is_tax_unit_spouse\": False, \"is_tax_unit_dependent\": False\n }\n for var in pe_vars:\n if var in row and pd.notna(row[var]):\n head[var] = row[var]\n persons.append(head)\n \n # Spouse (only for JOINT)\n if filing_status == 2:\n spouse_age = int(row[\"age_spouse\"]) if \"age_spouse\" in row and pd.notna(row.get(\"age_spouse\")) else age_head\n spouse = {\n \"person_id\": pid + 1, \"household_id\": household_id, \"tax_unit_id\": tax_unit_id,\n \"year\": year, \"state_code\": state_code, \"age\": spouse_age,\n \"is_tax_unit_head\": False, \"is_tax_unit_spouse\": True, \"is_tax_unit_dependent\": False\n }\n for var in household_vars | tax_unit_vars:\n if var in row and pd.notna(row[var]):\n spouse[var] = row[var]\n persons.append(spouse)\n \n # Dependents\n for i, dep_age in enumerate(dep_ages[:num_deps]):\n dep = {\n \"person_id\": pid + 2 + i, \"household_id\": household_id, \"tax_unit_id\": tax_unit_id,\n \"year\": year, \"state_code\": state_code, \"age\": dep_age,\n \"is_tax_unit_head\": False, \"is_tax_unit_spouse\": False, \"is_tax_unit_dependent\": True\n }\n for var in household_vars | tax_unit_vars:\n if var in row and pd.notna(row[var]):\n dep[var] = row[var]\n persons.append(dep)\n \n result = pd.DataFrame(persons)\n for var in person_vars:\n if var in result.columns:\n result[var] = result[var].fillna(0)\n return result\n\n\ndef parse_household_format(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"Parse household-level input (one row per household with multiple tax units).\"\"\"\n required = [\"household_id\", \"year\", \"num_tax_units\"]\n missing = [c for c in required if c not in df.columns]\n if missing:\n raise ValueError(f\"Missing required columns: {missing}\")\n \n persons = []\n for idx, row in df.iterrows():\n household_id = row[\"household_id\"]\n year = int(row[\"year\"])\n state_code = str(row[\"state_code\"]).upper() if \"state_code\" in row and pd.notna(row.get(\"state_code\")) else \"CA\"\n pid = idx * 1000\n \n for tu_num in range(1, int(row[\"num_tax_units\"]) + 1):\n s = f\"_{tu_num}\"\n tax_unit_id = household_id * 100 + tu_num\n \n filing_status_val = row.get(f\"filing_status{s}\")\n if pd.isna(filing_status_val):\n filing_status = 1\n elif isinstance(filing_status_val, str):\n filing_status = FILING_STATUS_MAP.get(filing_status_val.upper(), 1)\n else:\n filing_status = int(filing_status_val)\n \n age_head = int(row[f\"age_head{s}\"]) if f\"age_head{s}\" in row and pd.notna(row.get(f\"age_head{s}\")) else 40\n age_spouse = int(row[f\"age_spouse{s}\"]) if f\"age_spouse{s}\" in row and pd.notna(row.get(f\"age_spouse{s}\")) else 0\n num_deps = int(row[f\"num_dependents{s}\"]) if f\"num_dependents{s}\" in row and pd.notna(row.get(f\"num_dependents{s}\")) else 0\n \n persons.append({\n \"person_id\": pid, \"household_id\": household_id, \"tax_unit_id\": tax_unit_id,\n \"year\": year, \"state_code\": state_code, \"age\": age_head,\n \"is_tax_unit_head\": True, \"is_tax_unit_spouse\": False, \"is_tax_unit_dependent\": False\n })\n pid += 1\n \n if filing_status == 2 and age_spouse > 0:\n persons.append({\n \"person_id\": pid, \"household_id\": household_id, \"tax_unit_id\": tax_unit_id,\n \"year\": year, \"state_code\": state_code, \"age\": age_spouse,\n \"is_tax_unit_head\": False, \"is_tax_unit_spouse\": True, \"is_tax_unit_dependent\": False\n })\n pid += 1\n \n for _ in range(num_deps):\n persons.append({\n \"person_id\": pid, \"household_id\": household_id, \"tax_unit_id\": tax_unit_id,\n \"year\": year, \"state_code\": state_code, \"age\": 10,\n \"is_tax_unit_head\": False, \"is_tax_unit_spouse\": False, \"is_tax_unit_dependent\": True\n })\n pid += 1\n \n return pd.DataFrame(persons)\n\n\ndef parse_input(df: pd.DataFrame, input_type: str) -> pd.DataFrame:\n \"\"\"Route to appropriate parser based on input format type.\"\"\"\n parsers = {\"person\": parse_person_format, \"tax_unit\": parse_tax_unit_format, \"household\": parse_household_format}\n if input_type not in parsers:\n raise ValueError(f\"Unknown input type: {input_type}. Must be one of {list(parsers.keys())}\")\n return parsers[input_type](df)" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# =============================================================================\n# PolicyEngine Dataset Class\n# =============================================================================\n\n\nclass ResearcherDataset(Dataset):\n \"\"\"Converts person-level DataFrame into PolicyEngine's TIME_PERIOD_ARRAYS format.\"\"\"\n\n name = \"researcher_dataset\"\n label = \"Researcher Flat File Dataset\"\n data_format = Dataset.TIME_PERIOD_ARRAYS\n\n def __init__(self, person_df: pd.DataFrame):\n self.person_df = person_df.copy()\n self.tmp_file = tempfile.NamedTemporaryFile(suffix=\".h5\", delete=False)\n self.file_path = Path(self.tmp_file.name)\n super().__init__()\n\n def generate(self) -> None:\n data = {}\n years = sorted(self.person_df[\"year\"].unique())\n \n # Identify PE variable columns\n structural = {\"person_id\", \"household_id\", \"tax_unit_id\", \"year\", \"state_code\", \"age\",\n \"is_tax_unit_head\", \"is_tax_unit_spouse\", \"is_tax_unit_dependent\"}\n pe_vars = set(self.person_df.columns) - structural\n household_vars = {v for v in pe_vars if get_variable_entity(v) == \"household\"}\n tax_unit_vars = {v for v in pe_vars if get_variable_entity(v) == \"tax_unit\"}\n person_vars = pe_vars - household_vars - tax_unit_vars\n\n print(f\"Generating dataset for {len(self.person_df)} persons across {len(years)} year(s)...\")\n\n for year in tqdm(years, desc=\"Processing years\"):\n year_int = int(year)\n year_df = self.person_df[self.person_df[\"year\"] == year].copy()\n if len(year_df) == 0:\n continue\n\n n_persons = len(year_df)\n hh_map = {hid: i for i, hid in enumerate(year_df[\"household_id\"].unique())}\n tu_map = {tuid: i for i, tuid in enumerate(year_df[\"tax_unit_id\"].unique())}\n n_hh, n_tu = len(hh_map), len(tu_map)\n\n # Person-to-entity mappings\n person_hh = np.array([hh_map[h] for h in year_df[\"household_id\"]])\n person_tu = np.array([tu_map[t] for t in year_df[\"tax_unit_id\"]])\n \n data.setdefault(\"person_id\", {})[year_int] = np.arange(n_persons)\n data.setdefault(\"person_household_id\", {})[year_int] = person_hh\n data.setdefault(\"person_tax_unit_id\", {})[year_int] = person_tu\n for entity in [\"family\", \"spm_unit\", \"marital_unit\"]:\n data.setdefault(f\"person_{entity}_id\", {})[year_int] = person_hh\n\n # Entity ID arrays\n data.setdefault(\"household_id\", {})[year_int] = np.arange(n_hh)\n data.setdefault(\"tax_unit_id\", {})[year_int] = np.arange(n_tu)\n for entity in [\"family\", \"spm_unit\", \"marital_unit\"]:\n data.setdefault(f\"{entity}_id\", {})[year_int] = np.arange(n_hh)\n\n # Person attributes\n data.setdefault(\"age\", {})[year_int] = year_df[\"age\"].values.astype(int)\n for role in [\"is_tax_unit_head\", \"is_tax_unit_spouse\", \"is_tax_unit_dependent\"]:\n data.setdefault(role, {})[year_int] = year_df[role].values.astype(bool)\n\n # State FIPS (household-level)\n hh_states = year_df.groupby(\"household_id\")[\"state_code\"].first()\n state_codes = [hh_states[h] for h in sorted(hh_map.keys(), key=lambda x: hh_map[x])]\n data.setdefault(\"state_fips\", {})[year_int] = np.array([STATE_CODE_TO_FIPS.get(sc, 6) for sc in state_codes])\n\n # Person-level PE variables\n for var in person_vars:\n if var in year_df.columns:\n data.setdefault(var, {})[year_int] = year_df[var].fillna(0).values.astype(float)\n \n # Household-level PE variables\n for var in household_vars:\n if var in year_df.columns:\n hh_vals = year_df.groupby(\"household_id\")[var].first()\n vals = [hh_vals.get(h, False) for h in sorted(hh_map.keys(), key=lambda x: hh_map[x])]\n pe_var = _TAX_BENEFIT_SYSTEM.variables.get(var)\n dtype = bool if pe_var and pe_var.value_type == bool else float\n data.setdefault(var, {})[year_int] = np.array(vals).astype(dtype)\n \n # Tax unit-level PE variables\n for var in tax_unit_vars:\n if var in year_df.columns:\n tu_vals = year_df.groupby(\"tax_unit_id\")[var].first()\n vals = [tu_vals.get(t, 0) for t in sorted(tu_map.keys(), key=lambda x: tu_map[x])]\n pe_var = _TAX_BENEFIT_SYSTEM.variables.get(var)\n if pe_var and pe_var.value_type == bool:\n dtype = bool\n elif pe_var and pe_var.value_type == int:\n dtype = int\n else:\n dtype = float\n data.setdefault(var, {})[year_int] = np.array(vals).astype(dtype)\n\n self.save_dataset(data)\n print(\"Dataset generated successfully.\")\n\n def cleanup(self) -> None:\n if hasattr(self, \"file_path\") and self.file_path.exists():\n try:\n self.file_path.unlink()\n except:\n pass" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# =============================================================================\n# Main Simulation Function\n# =============================================================================\n\n\ndef run_microsim(\n input_file: str,\n input_type: str = \"tax_unit\",\n output_vars: list = None,\n output_file: str = None,\n) -> pd.DataFrame:\n \"\"\"\n Run PolicyEngine microsimulation on a flat-file dataset.\n \n This is the main entry point. It:\n 1. Reads and parses your CSV into person-level format\n 2. Creates a PolicyEngine Dataset\n 3. Runs the simulation\n 4. Extracts results per tax unit\n \n Args:\n input_file: Path to CSV file\n input_type: Format of input data:\n - \"tax_unit\": One row per tax unit (most common)\n - \"person\": One row per person (for split income)\n - \"household\": One row per household with multiple tax units\n output_vars: List of PolicyEngine variables to calculate\n (default: [\"income_tax\", \"state_income_tax\"])\n output_file: Optional path to save results CSV\n \n Returns:\n DataFrame with one row per tax unit and requested output variables\n \"\"\"\n if output_vars is None:\n output_vars = [\"income_tax\", \"state_income_tax\"]\n \n print(f\"Input: {input_file}\")\n print(f\"Format: {input_type}\")\n print(f\"Outputs: {output_vars}\")\n \n # Step 1: Read and parse input\n input_df = pd.read_csv(input_file)\n print(f\"Read {len(input_df)} rows\")\n \n person_df = parse_input(input_df, input_type)\n print(f\"Expanded to {len(person_df)} persons\")\n \n # Step 2: Create dataset and run simulation\n dataset = ResearcherDataset(person_df)\n \n try:\n dataset.generate()\n sim = Microsimulation(dataset=dataset)\n \n # Step 3: Extract results per tax unit\n results = []\n years = sorted(person_df[\"year\"].unique())\n \n for year in tqdm(years, desc=\"Extracting results\"):\n year_int = int(year)\n year_str = str(year_int)\n year_df = person_df[person_df[\"year\"] == year]\n tax_units = year_df.groupby(\"tax_unit_id\").first().reset_index()\n \n for idx, (_, tu_row) in enumerate(tax_units.iterrows()):\n result = {\n \"tax_unit_id\": tu_row[\"tax_unit_id\"],\n \"year\": year_int,\n \"state_code\": tu_row[\"state_code\"],\n }\n for var in output_vars:\n try:\n values = sim.calculate(var, period=year_str)\n result[var] = round(float(values[idx]), 2)\n except Exception as e:\n print(f\"Warning: Could not calculate {var}: {e}\")\n result[var] = 0.0\n results.append(result)\n \n results_df = pd.DataFrame(results)\n \n # Step 4: Save if output file specified\n if output_file:\n results_df.to_csv(output_file, index=False)\n print(f\"Results saved to {output_file}\")\n \n print(f\"Done! {len(results_df)} tax units processed.\")\n return results_df\n \n finally:\n dataset.cleanup()" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n## Input Format Reference\n\n### Tax Unit Format (`input_type=\"tax_unit\"`) - Most Common\n\nOne row per tax filing unit. Minimal example:\n\n```csv\ntax_unit_id,year,employment_income\n1,2024,50000\n2,2024,75000\n```\n\n**Required:** `tax_unit_id`, `year`\n\n**Optional:** `state_code`, `filing_status`, `age_head`, `age_spouse`, `num_dependents`, `dependent_ages`, plus any PolicyEngine variable\n\n---\n\n### Person Format (`input_type=\"person\"`) - Split Income\n\nOne row per person. Use when each person has different income.\n\n**Required:** `person_id`, `household_id`, `tax_unit_id`, `year`, `age`, `is_tax_unit_head`, `is_tax_unit_spouse`, `is_tax_unit_dependent`\n\n---\n\n### Household Format (`input_type=\"household\"`) - Multiple Tax Units\n\n**Required:** `household_id`, `year`, `num_tax_units`\n\n---\n\n### Common Variables\n\n**Inputs** (add as columns - entity is auto-detected):\n- `employment_income`, `self_employment_income`, `social_security`\n- `long_term_capital_gains`, `taxable_interest_income`, `rental_income`\n- `in_nyc` (household-level, True/False)\n\n**Outputs** (request via `output_vars`):\n- `income_tax`, `state_income_tax`, `eitc`, `ctc`\n- `adjusted_gross_income`, `taxable_income`, `payroll_tax`\n\nFull list: [policyengine.org/us/api/variables](https://policyengine.org/us/api/variables)" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n## Run\n\nReplace `your_data.csv` with your file path:" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Run microsimulation on your CSV file\nresults = run_microsim(\n input_file=\"your_data.csv\",\n output_vars=[\"income_tax\", \"state_income_tax\", \"eitc\", \"ctc\"],\n)\nresults" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file From 22f7b92006b941c0d1ce3b340ea3684043159a0d Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Sun, 4 Jan 2026 17:04:26 -0500 Subject: [PATCH 2/2] name --- run_microsim.ipynb => run_microsim_over_flat_file.ipynb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename run_microsim.ipynb => run_microsim_over_flat_file.ipynb (100%) diff --git a/run_microsim.ipynb b/run_microsim_over_flat_file.ipynb similarity index 100% rename from run_microsim.ipynb rename to run_microsim_over_flat_file.ipynb