# MacroVision: methods notes (simulation model and empirical pipeline, as implemented)

Source: this repository. Script paths are relative to `scripts/` unless stated. `file:line` cites the line that defines each item. The household model is copied into several files (`saving_asset_sensitivity_v73`, `integrated_housing_v76`, `credit_resolution_v77`, `corporate_credit_model_v98`, `corporate_workout_model_v100`). The final results use **`corporate_credit_model_v98.py` (cited as `CCM98`)** and **`corporate_workout_model_v100.py` (`CWM100`)**. `CWM100` matches `CCM98` line for line except for the workout additions (§4.4). Its line numbers are +1 from line 10 and +3 from line 45 onward, up to line 137.

All quantities are in normalized units: base-year nominal output = 100, workforce = 1, base price level = 1. They are not currency amounts or a calibrated country (coupled_thesis_v71.py:118). Every output is flagged `illustrative: True, calibrated: False`.

---

## A. Simulation model

### 1. Time structure and exogenous paths

- The horizon is `years = 20` and the transition lasts `transition_years = 10` (coupled_thesis_v71.py:17). Years run from t = 0 to 20, giving 21 annual observations (v71:37).
- Automation progress is a_t = min(1, t / 10) (v71:38).
- Unemployment is imposed: u_t = 0.20 · a_t (v71:39; `terminal_unemployment = .20`, v71:17). With η = 0 the capital feedback leaves it unchanged, which test_macro_sensitivity_v96.py:20 checks (u_t = 20·min(t/10,1) %).
- Efficiency (productivity) is e_t = 1 + g · a_t, with g = `productivity_gain` (default .50, grid {.25,.5,1}) (v71:40).
- The wage index is w_t = 1 + `wage_gain`·a_t, with `wage_gain = 0` throughout, so w_t = 1 (v71:41, :17).
- Year 0 is a reference year. At t = 0 there is no fiscal settlement, no asset trade, no investment and no balance update (CCM98:75, :107–109, :143–144). Year-0 cash flows are computed but are not booked to stocks (`new_cash=dict(cash)`, CCM98:109).

### 2. Macro core (`coupled_thesis_v71.simulate`)

**Constants** (v71:8–16):

| Constant | Value |
|---|---|
| `BASE_OUTPUT` | 100.0 |
| `BASE_WAGES` | 60.0 |
| `BASE_PROFITS` | 100 − 60 = 40.0 |
| `WAGE_TAX` | .20 |
| `BASE_PROFIT_TAX` | .25 |
| `INITIAL_GOV_NET_DEBT` | 60.0 |
| `INTEREST` (government rate) | .03 |
| `BASE_REVENUE` | .20·60 + .25·40 = 22.0 |
| `EXISTING_REAL_SPENDING` | 22.0 − .03·60 = 20.2 |

Other defaults (v71:17) are `pass_through = .75`, `profit_tax_rate = .25`, `capture_share = 1.`, `debt_service = 0.`, and the bounds are at v71:18. The grid (§6) overrides pass-through, tax rate, capture share and productivity gain.

**Equations, year t** (v71:42–52):
- Real output: Y^r_t = e_t (1 − u_t) (v71:42)
- **Price index:** P_t = 1 − π · (BASE_WAGES/BASE_OUTPUT) · (1 − w_t / e_t) = 1 − π·0.6·(1 − 1/e_t), where π = `pass_through` (v71:43). This is the share of unit-labour-cost savings passed into prices. For example, at g = 1, π = 1 the terminal value is P = 0.7.
- Nominal output: Y_t = 100 · P_t · Y^r_t (v71:44)
- Wage bill: W_t = 60 · w_t · (1 − u_t) (v71:45). The nominal wage per employed worker is constant.
- Profits: Π_t = Y_t − W_t (v71:46)
- Profit-tax rate (linear ramp): τ^π_t = .25 + a_t (τ̄^π − .25) (v71:47)
- Capture share (linear ramp): κ_t = 1 + a_t (κ̄ − 1) (v71:48)
- Wage tax: T^w_t = .20 · W_t (v71:49)
- Profit tax: T^π_t = τ^π_t · κ_t · max(0, Π_t) (v71:50)
- Revenue: R_t = T^w_t + T^π_t (v71:51)
- Distributable profit: `potential_profit_after_cash_tax` = Π_t − T^π_t (v71:69). All of it is paid out as dividends (§3.3).
- Existing government purchases: G_t = 20.2 · P_t (v71:52; repeated in CCM98:67)
- Government interest: 0.03 × outstanding bonds (CCM98:67)

**Important:** v71 also contains its own benefit and debt recursion (v71:54–62: `debt = beginning_debt − overall`). The final path calls `simulate(p,'budget')` (CCM98:47) **only for the production and revenue rows**. Government debt is rebuilt inside the household model under a no-new-borrowing rule (§3.4), so v71's debt recursion is **not** used in the final results.

### 3. Households (fixed cohorts)

#### 3.1 Cohorts, ownership and initial stocks

- Cohort masses (`MASS`, household_distribution_v72.py:7): `workers` .70, `displaced_free` .10 (no initial consumer debt), `displaced_debt` .10, `owners` .10.
- Equity ownership schemes (`SCHEMES`, v72:12):
  - `concentrated`: owners hold 1.0 unit and everyone else 0.
  - `broad`: units equal cohort masses.
- `REAL_EQUITY = 200.` (v72:9). The constant-real quote is q_t = 200·P_t per unit (CCM98:26), and total units start at 1.
- Consumer loan: `INITIAL_PRIVATE_LOAN = 20.` in aggregate, which is 200 per debtor (v72:10). It is held by owners, owed by `displaced_debt`, and is interest-only at `PRIVATE_RATE = .04` (v72:11).
- Interest due is .04 × the **nominal** balance (CCM98:66). The loan is not indexed, so the real burden rises when P falls.
- Government bonds of 60 are held by owners (CCM98:49, :54).
- Household cash starts at `INITIAL_CASH = 20.` × mass for each cohort, 20 in aggregate (CCM98:12, :48). Other initial stocks: `INITIAL_FIRM_CASH = 30.` and `INITIAL_REAL_CAPITAL = 200.` (CCM98:13–14).
- Reference disposable income (v72:16–26) is (1 − .2)·60 + capital income + financial income − debt due:
  - Capital income = 40·(1 − .25)·share/mass (v72:20).
  - Owners' financial income = (.03·60 + .04·20)/.1 (v72:22).
  - Debt due = .04·20/.1 = 8 for `displaced_debt` (v72:21).
  - Computed values: concentrated = {workers 48, displaced_free 48, displaced_debt 40, owners 374}; broad = {78, 78, 70, 104}.

#### 3.2 Employment and wages
- Displaced cohorts: employment = 1 − u_t/.2. Workers and owners: employment = 1 (CCM98:63). The 0.2 is hard-coded, so at u = .2 both displaced cohorts are fully unemployed.
- After-tax wage per employed worker: (1 − .2) W_t/(1 − u_t) = 48 (CCM98:64).

#### 3.3 Income components
- **Wages:** mass · employment · wage (CCM98:77).
- **Dividends:** (Π − T^π) × units_k / Σunits (CCM98:65).
- **Benefits:** see §3.4.
- **Government interest:** paid to owners (CCM98:78).
- **Consumer-loan interest:**
  - `debt_resources` = cash + gross income + q·units (CCM98:85).
  - paid = min(due, max(0, `debt_resources`)); the unpaid part is capitalized into the loan (CCM98:86, :111).
  - Paid interest is income to owners (CCM98:87).
- **Corporate-credit interest:** pro rata to claim holders (CCM98:82–83; §4.3).
- **Housing income:** rent and mortgage interest move from payers to owners (credit_ledger_v77.py:46).
- **Net income:** gross − contractual due (debtor) + housing income (CCM98:88, :98).

#### 3.4 Benefit rule and government finance
- Required benefit for each displaced cohort k (CCM98:69–71):
  B*_k = MASS_k (1 − emp_k) · max(0, P_t·ref_k − div_k/MASS_k + [due/MASS_k if k = displaced_debt]).
  The target is the price-indexed reference income less own dividends, plus current contractual consumer interest for debtors.
- Fiscal room: avail_t = R_t − G_t − 0.03·bonds_t (CCM98:72).
- Budget-limited fraction: f_t = 1 if ΣB* = 0, otherwise min(1, max(0, avail/ΣB*)) (CCM98:73). Paid benefits are f·B*_k (CCM98:74).
- Settlement, `fiscal_settlement` (CCM98:32–35; applied at :75 for t > 0):
  - surplus = avail − benefits;
  - redemption = min(bonds, max(0, surplus));
  - bonds ← bonds − redemption;
  - treasury ← treasury + surplus − redemption.
- **The government cannot issue new debt.** Treasury cash starts at 0 and earns nothing. If treasury < −1e-8, the path stops (CCM98:76).
- Redemption cash goes to owners (CCM98:89).
- Reported net debt is bonds − treasury (CCM98:159).

#### 3.5 Consumption/saving rule (`saving='income_share'`, the default; the only rule used in the grid)
- Spendable income (CCM98:100) = net income − maintenance − (mortgage principal paid + deficiency paid). Principal is treated as an asset exchange, not consumption.
- Year 0 deducts the *scheduled* principal instead of the principal paid (CCM98:102). The per-capita reference is `initial_after_housing` = max(0, spendable_0)/mass (CCM98:103).
- Desired consumption: C*_k = 0.9 · max(0, spendable_k) (`CONSUME_SHARE = .9`, CCM98:15, :104).
- Cohorts are processed in the order debtor, workers, displaced_free, owners (CCM98:113). Owners' resources include the debtor's principal repayment.
- Actual consumption: C_k = min(C*_k, max(0, cash resource + q·units_k)) (CCM98:115). Households sell equity at q to fund any shortfall below zero cash (CCM98:117–119).
- Any surplus above a cash buffer of `BUFFER_YEARS = .25` × C*_k is invested in equity at q (CCM98:121–126). The debtor first repays `REPAY_SHARE = .5` × the investable surplus on the consumer loan (CCM98:16–17, :124).
- The alternative rule `smooth_reference` (CCM98:104) is **not used** in v96, v98 or v100.

#### 3.6 Housing tenure and stress (credit_ledger_v77.py)
- Tenure options (`TENURES`, ledger:9), initial state at ledger:11–16:
  - `renter`: owners own all homes.
  - `owner_free`: each cohort owns homes equal to its mass, with no mortgage.
  - `mortgage_fixed` and `mortgage_reset`: like `owner_free`, but non-owners carry a mortgage of 150 × mass.
- House price: 200·(1 + (H̄ − 1)·a_t) (ledger:25).
- Mortgage rate: .04, or .04 + (r̄ − .04)·a_t for `mortgage_reset` (ledger:26).
- Rent: 12·(1 + (R̄ − 1)·a_t) per unit mass (ledger:27).
- Maintenance: 2 per home (ledger:34).
- Stresses (H̄, r̄, R̄) are defined at housing_stress_v75.py:8 and ledger:5. The grid uses only `stable` (1, .04, 1) and `rate_shock` (.7, .08, 1). `credit_stress` (.4, .12, 1) is not used.
- Mortgage payment: an annuity recast each year over the remaining term max(1, 26 − t), or 25 at t = 0 (v75:11–13; ledger:37).
- Payment waterfall per non-owner cohort, from resources that include liquidatable equity (ledger:31–42):
  1. rent;
  2. maintenance;
  3. interest;
  4. scheduled principal;
  5. 20% of remaining resources toward any deficiency.
- Unpaid interest is capitalized (ledger:49) and unpaid rent becomes rent debt.
- The owners cohort is the landlord and mortgage lender. There is no bank.

#### 3.7 Credit resolution (`resolution=True, default_years=2, cost_rate=.1, recourse=0.`; CCM98:38)
- Mortgage streak: a year counts if the shortfall exceeds max(1e-8, .1·due) (ledger:55).
- After 2 consecutive years, foreclosure (ledger:57–64):
  - Collateral = house price × homes; cost = 10% of collateral.
  - Recovery = min(claim, collateral − cost). Recourse 0 means the remaining gap is written off.
  - The home transfers to owners and the borrower becomes a renter. Any surplus is paid to the borrower.
  - The cost is paid by owners as maintenance (ledger:74), so it counts as consumption, i.e. demand for production.
- Rent debt is written off after a 2-year streak (ledger:56, :67–68).
- Consumer loan: written off entirely after 2 consecutive years in which unpaid interest exceeds max(1e-8, .1·due) (CCM98:137–139).

### 4. Firm side

#### 4.1 Capital feedback, `feedback(.3, 0.)` (capital_feedback_v74.py:17–35)
- Valid ranges are 0 ≤ α < 1 and |η| ≤ .5 (v74:18). Let k_t = K_t/200, with beginning-of-year capital (v74:22).
- Labour: L = min(1, max(.8, (1 − u)·k^η)) (v74:25). With **η = 0**, L = 1 − u: the capital–labour channel is switched off, so unemployment stays on the imposed path.
- Output: Y_t ← Y_t · k^α · (L/(1 − u))^(1−α), with **α = .3** (v74:26). Capital scales nominal output, while the price index from v71 is unchanged.
- Wages: W_t ← W_t · L/(1 − u), which is unchanged when η = 0 (v74:27).
- Profits are recomputed as Π = Y − W, and profit tax is recomputed with the same ramps (v74:28–30). Revenue, profit after tax and output are overwritten (v74:31–33).
- Ambiguity: the overwritten `real_output_index` is Y/P (v74:31), which is not ×100-normalized as it is in v71. Because base output is 100, the scale happens to coincide.

#### 4.2 Investment and firm cash (t > 0)
- Investment is a residual from the resource identity: I_t = Y_t − G_t − ΣC_k − Σmaintenance (CCM98:128).
- Firm cash: F ← F − I + net equity cash (CCM98:129–130). Net equity cash is the net household purchases of units at q, i.e. issuance minus redemption.
- Capital: K ← K + I/P (CCM98:131).
- Profits are fully paid out, so operating cash equals C + G − W − T − D = −I. That is why only I and equity flows move firm cash.

#### 4.3 Corporate credit (corporate_credit_ledger_v98.py; terms at corporate_credit_v98.py:11)
- `service`, at the start of t > 0 (ledger:4–13; CCM98:82):
  - Interest due = rate × claims; paid = min(F, due).
  - Principal = min(remaining F, amortization × claims).
  - Unpaid interest is capitalized pro rata.
- `advance`, after investment (ledger:15–20; CCM98:132):
  - If F < 0, draw = min(−F, Σ positive household cash, limit − outstanding).
  - The draw is taken pro rata from household cash, which becomes claims. There is no money creation.
- Wealth consolidation nets the claims against equity holders (CCM98:149).
- `TERMS`, as (limit, rate, amortization): `disabled` (0, .04, .1), `limited_20` (20, .04, .1), `limited_60` (60, .04, .1), `rate_08` (60, .08, .1).

#### 4.4 Workout (`CWM100`; corporate_workout_ledger_v100.py)
- A year counts as missed if capitalized interest > 1e-8 or principal paid + 1e-8 < amortization × opening claims (CWM100:140–141).
- When the streak reaches `workout_years` ∈ {1, 2, 3} and claims > 1e-8, a debt-for-equity `exchange` takes place at q (CWM100:142–144):
  - Enterprise value V = q × Σunits.
  - Recovery = min(V, D).
  - Old holders keep a fraction max(0, 1 − D/V).
  - Lenders receive Σunits · (recovery/V) · claim_k/D.
  - Lender mark loss = claim · (1 − recovery/D).
  - No cash moves and total units are unchanged. Both properties are asserted at workout ledger:4–18.
- After an exchange, credit is closed and the advance limit is 0 (CWM100:135).
- Order of steps: the workout check runs after the failure check and before consumer resolution (CWM100:136–146).
- Fixed terms are limit 60, rate .04, amortization .1 (corporate_workout_v100.py:16). With `workout_years = 0` (no workout) the run is identical to `limited_60` (test_corporate_workout_v100.py:22–23; asserted for completion at thesis_synthesis_v101.py:31–32).

### 5. Stop conditions, completion and control
Failure reasons are exact strings, checked in this order:
1. `'No outstanding equity claims'`: Σunits ≤ 1e-12 (CCM98:60).
2. `'Zero equity quote: liquidation unavailable'`: q ≤ 1e-12 (CCM98:62). This cannot occur under `constant_real`, since P ≥ 0.7 > 0.
3. `'Existing government commitments lack cash financing'`: treasury < −1e-8 (CCM98:76).
4. `'Lender lacks cash/equity to finance foreclosure services and borrower surplus'`: raised by `ResolutionFundingError` (ledger:72–73; CCM98:94–95).
5. `'Corporate liquidity, real capital or equity-redemption capacity exhausted'`: after credit advance, firm cash < −1e-8, K < −1e-8, any cohort's units < −1e-8, or Σunits ≤ 1e-12 (CCM98:133–134).

- **Completed** means no failure and the last row has year = 20 (CCM98:158). Failed paths are truncated and never imputed.
- **Control** (`control=True`, CCM98:44) sets `terminal_unemployment = 0` and `productivity_gain = 0`. As a result u ≡ 0, e ≡ 1, P ≡ 1 and no benefits are paid. The control **keeps** the tax-rate and capture ramps, ownership, tenure and stress. The repo itself labels it "no-displacement/no-productivity control under matched policies, not a no-policy-change control" (macro_sensitivity_v96.py:63).
- Consequence: because the capture ramp is kept, a control with κ̄ = .5 has revenue 12 + .25·.5·40 = 17, which is below G + interest = 20.2 + 1.8. So many controls stop on reason 3.

### 6. Outcomes, variants and grid
- **Consumption index:** 100 · (C_k/MASS_k/P_t) / (0.9 · `initial_after_housing`_k) (CCM98:150). This is per-capita real non-housing consumption relative to the cohort's own year-0 consumption, so it equals 100 at t = 0. It is None if the year-0 after-housing income is ≤ 0.
- **Terminal real consumption:** C_k/MASS_k/P at t = 20 (`consumption_real`). It is None unless the path completed (v96:39).
- **`preserves_90_all_years`:** true when the index is non-null and ≥ 90 − 1e-8 in all 21 years. It is None if the path is not completed (v96:41, :64).
- **Matched-control gap:** actual minus control terminal real consumption, computed only when both paths completed (v96:49–50).
- **The 576-case grid** (`FACTORS`, v96:10–14) is the full factorial 3·3·2·2·2·4·2 = 576:
  - `productivity_gain` {.25, .5, 1.}
  - `pass_through` {0., .5, 1.}
  - `profit_tax_rate` {.25, .35}
  - `capture_share` {.5, 1.}
  - `ownership` {concentrated, broad}
  - `tenure` {renter, owner_free, mortgage_fixed, mortgage_reset}
  - `stress` {stable, rate_shock}
- **Fixed in the grid** (v96:62):
  - α = .3, η = 0;
  - `saving` = income_share, quote = constant_real;
  - terminal unemployment .2, horizon 20 years;
  - resolution on, recourse 0.
- **Seven variants** (v101:7, :28–29): `no_credit` (= `disabled`), `credit_20_04`, `credit_60_04`, `credit_60_08`, and `workout_1`, `workout_2`, `workout_3` (60/.04/.1 credit followed by an exchange after 1, 2 or 3 missed years).
- Each variant is run as actual and control, giving 576 × 7 × 2 paths. Cases are matched by exact parameter tuple; the script asserts 576 cases in each source (v101:22–24).
- **v101 per-cohort classification** (v101:10–17), based on the actual paths only:
  - `no_complete_variant`;
  - `partial_completion_unresolved` (some variants incomplete);
  - `preserved_in_all`;
  - `not_preserved_in_any`;
  - `varies_across_complete_variants`.
- Gap signs are computed only when all 14 paths of a case complete (v101:34, :38).
- Recorded counts, for context only (read from the stored JSON, not re-run): 202 cases complete in all 7 variants, 18 in some, 356 in none, and 42 are fully paired.

### 7. Accounting checks and tests
- **Money identity:** Σ household cash + firm cash + treasury − initial money (= 20 + 30), recorded each year (CCM98:151). The maximum over the path is reported as `max_money_error` (CCM98:159).
- The v98 and v100 drivers **assert** `max_money_error < 1e-8` and corporate-credit `balance_error < 1e-8` on every path (corporate_credit_v98.py:27–28; corporate_workout_v100.py:26–27).
- `balance_error` is closing claims − (opening + capitalized − principal + draw − converted) (CCM98:152; CWM100:163).
- The exchange asserts unit and value conservation (workout ledger:14–15).
- Housing `cash_transfer_error` is recorded (ledger:77) but **not asserted** anywhere.
- Tests (all 13 pass; run here with `python -m unittest`):
  - `test_macro_sensitivity_v96.py`: control equals an explicit zero-transition run; money error < 1e-8; imposed u path; Y = G + C + I for t > 0 (line 22); failure leads to null terminal outcomes.
  - `test_corporate_credit_v98.py`: `advance` and `service` arithmetic; `disabled` reproduces v96; money and credit balances; claims net to zero against equity shares.
  - `test_corporate_workout_v100.py`: solvent and underwater exchange waterfalls; workout 0 equals v98 `limited_60`; no money creation; no draws after a workout.
  - `test_thesis_synthesis_v101.py`: a truncated path can never be classified as preserved or failed.

---

## B. Empirical pipeline (descriptive, flagged `causal_claims_allowed: False`)

### 1. Data sources
- **Enterprise AI adoption**: Eurostat `isoc_eb_ai`, indicator `E_AI_TANY` (enterprise uses at least one AI technology), unit `PC_ENT` (% of enterprises). Coverage: enterprises with 10+ employees (`size_emp=GE10`), NACE `C10-S951_X_K`, annual 2023–2025, 14 countries (AT BE CZ DE DK ES FI FR IE IT NL PL PT SE), 42 observations. Three observations carry the `b` (break) flag: FR-2023, NL-2025, SE-2023 (data/processed/eurostat_ai_14countries_manifest_v148.json).
- **Occupation employment**: Eurostat LFS `lfsa_egai2d`, in thousands of persons (`THS_PER`), ages 15–64, both sexes, ISCO-08 two-digit, 2018–2025, the same 14 countries. The file has 4,816 rows, 259 of them missing, covering 43 occupation groups (eurostat_occupation_verified_manifest_v166.json).
- **Adjacent-year pairs** (occupation_break_policy_v168.py:9–35): 4,214 pairs. A pair is eligible for the primary analysis unless:
  - it spans 2020→2021 (IESS break);
  - either year's flag contains `b` or `u`;
  - employment is missing.
  - Pairs with only a `d` flag stay eligible.
- **Exposure**: ILO Gmyrek et al. (2025), "Generative AI and Jobs: A Refined Global Index of Occupational Exposure", workbook `Final_Scores_ISCO08_Gmyrek_et_al_2025.xlsx` from the GitHub repo pgmyrek/2025_GenAI_scores_ISCO08 (review_ilo_exposure_workbook_v177.py:10). The 2-digit exposure is the **unweighted** mean of `mean_score_2025` over 427 four-digit occupations, giving 40 groups (aggregate_ilo_exposure_v180.py:30–39). ISCO 01–03 (armed forces) have no score (ilo_eurostat_occupation_panel_v183.manifest.json).

### 2. Sample construction (ai_adoption_exposure_panel_v190.py:15–52)
- Keep pairs with end year 2024 or 2025: **1,204** pairs (v190:34). Exclusions are applied sequentially:

| Step | Reason | Excluded |
|---|---|---|
| 1 | not primary-eligible (`employment_quality`, v190:36) | 249 |
| 2 | ISCO missing from exposure (`exposure_missing`, v190:37) | 33 |
| 3 | adoption missing for t or t−1 (v190:38) | 0 |
| 4 | either adoption year flagged (`adoption_source_flag`, v190:39) | 74 |
| 5 | employment at start ≤ 0 (v190:43) | 0 |

- **Final sample: N = 848** (405 with end year 2024, 443 with end year 2025): 13 countries, 39 occupations, 23 country-year cells.
- France drops out entirely because of LFS `b` flags in both transitions. DK-2024 is also excluded by `b` flags. The 74 adoption-flag exclusions are the NL-2025 and SE-2024 cells. These counts are reproduced by the v191 manifest and test.

### 3. Variables
- g_cot = 100·(L_end/L_start − 1): annual employment growth in percent (v190:46).
- E_o ∈ [0, 1]: ILO 2025 exposure, fixed over time.
- ΔA_ct = A_ct − A_c,t−1: change in adoption in percentage points (v190:47).
- Regressor: E_o × ΔA_ct (v190:48).
- Sample statistics (computed here): ΔA ranges 0.77–14.45 pp; mean g = 1.04 and SD = 8.22.

### 4. Estimating equation (ai_adoption_exposure_interaction_v192.py:77–92)
g_cot = α + β (E_o · ΔA_ct) + γ_ct + δ_o + ε_cot.

- Estimated by OLS with dummy-variable fixed effects: country-year (22 dummies) and occupation (38 dummies), one reference category dropped from each (v192:84–86). The country-year effects absorb ΔA and the occupation effects absorb E_o.
- A rank check is applied (v192:87). Minimum coverage is 500 rows, 30 occupations and 20 country-year cells (v192:80).
- Standard errors are cluster-robust by occupation (39 clusters), using a normal approximation (`use_t=False`) and statsmodels' default cluster small-sample correction (v192:88).
- Recorded result (data/processed/ai_adoption_exposure_interaction_results_v194.json): β = −0.5449, SE 0.5427, p = 0.315, 95% CI [−1.609, 0.519].

### 5. Robustness (ai_adoption_interaction_robustness_v195.py:14–35)
The same estimator is run on:
- **(a) Matched-cell panel**: country×occupation cells observed in both 2024 and 2025 (v195:17–22): 368 cells, N = 736.
- **(b) Leave-one-country-out**: 13 fits (v195:25–28).
- **(c) Leave-one-occupation-out**: 39 fits (v195:29–32).

The v195 output is archived in `data/processed/ai_adoption_interaction_robustness_v195.json` (see `AI_ADOPTION_ROBUSTNESS_V195_RESULTS.md`). An independent numpy replica of the cluster-robust OLS reproduces both v194 and the archived v195 numbers.

| Check | Result |
|---|---|
| Matched-cell panel | β = −0.686, p = 0.473 |
| Leave-one-country-out | β from −0.839 to −0.201; p from 0.082 to 0.712 |
| Leave-one-occupation-out | β from −0.818 to −0.332; p from 0.099 to 0.581 |

No specification reaches p < 0.05, and the sign never changes.

The tests (test_ai_adoption_interaction_v192.py, test_ai_adoption_interaction_robustness_v195.py at the repo root) only check that the design strings are present. They do not check the numbers.

### 6. Points to state as limitations or ambiguities
1. The sim uses v71 only for production and revenue; government debt dynamics come from the no-borrowing settlement.
2. Displaced employment is hard-coded as 1 − u/.2.
3. The nominal consumer loan versus the falling price level is a built-in channel.
4. Investment is a residual, not a decision rule.
5. The control keeps the policy ramps.
6. The v195 numbers were checked twice: archived CI output and an independent replica agree.
7. Exposure is unweighted and fixed in 2025; adoption is measured at the country level and contemporaneous with the employment change.
