The State Optimized the Dashboard and Lost the Citizen

The State Optimized the Dashboard and Lost the Citizen
Page content

An AI-assisted model of how GDP optics, housing pressure, debt rollover, and fiscal constraint can hide citizen insolvency.

1. A Country Is Its Citizens

A country is not its GDP, its bond market, its housing index, or its stock-market capitalization. A country is its citizens.

That sounds obvious enough to be useless, but most modern economic dashboards quietly forget it. They measure the state, the asset market, the tax base, the debt stock, the growth rate, the investment flow, the headline employment number, the budget balance, the bond spread, and the index level. They do not directly measure whether citizens can stand on their own.

Can a worker afford a home? Can a renter build savings? Can a young family form without inherited wealth? Can a person leave a bad employer or landlord without immediate collapse? Can the next generation enter adult life without starting from debt, rent, dependence, and delay?

Those are not sentimental questions. They are solvency questions.

If a state preserves its balance sheet by weakening the citizen balance sheet, then the country has not been preserved. The loss has merely been moved downward.

That is the principle behind this post:

A state cannot be solvent if its citizens are becoming insolvent.

The usual macroeconomic dashboard can miss this entirely. GDP can rise while rent consumes the household. Tax receipts can rise while savings collapse. House prices can rise while ownership access disappears. The state can still borrow. The market can still approve. The press release can still say growth.

But underneath the dashboard, the citizen may be losing the capacity to build an independent life.

That is the gap this post tries to model.

Not collapse, not conspiracy, not one villain: a wrong objective function.

Modern states increasingly optimize for dashboard continuity: GDP, asset prices, tax receipts, population growth, capital inflows, debt rollover, and investor confidence. Those numbers matter. But the citizen needs something else: housing access, real wage power, savings resilience, public-service capacity, domestic productive depth, and the ability to say no.

This post calls that missing object citizen solvency.

The question is simple: can a country look successful while its citizens become less solvent?

If the answer is yes, then GDP is not enough. We need a way to measure what the dashboard is missing.

The rest of this post turns that question into a model: not a metaphor, not a mood, but a structure that can be inspected, tested, and broken.


2. The Dashboard State

Every system has an objective function, whether it admits it or not.

For a state, that objective function is usually implied rather than declared. It is scattered across budgets, forecasts, central-bank language, development plans, fiscal rules, housing schemes, investor presentations, migration targets, and growth projections.

But the revealed objective function is visible in what the system protects under pressure.

Modern states protect a familiar cluster of dashboard variables: GDP growth, tax receipts, asset values, debt rollover, institutional confidence, capital inflows, and the appearance of continuity.

None of these is automatically bad.

A state needs revenue. It needs functioning markets. It needs investment. It needs debt markets not to panic. It needs enough continuity for households, firms, banks, public services, and investors to plan around.

The problem is not that these dashboard metrics are fake.

The problem is that they are incomplete.

Goodhart’s law gives the warning: when a measure becomes a target, it stops being a good measure. A system can learn to move the number without improving the underlying condition the number was meant to represent.

That is the danger of the Dashboard State.

The point is not that the state is literally an AI. It is not.

The point is structural: a state optimizing GDP, tax receipts, asset prices, and debt rollover can suffer the same class of failure as any optimizer that overfits a proxy. The reward signal improves. The underlying target diverges.

In an AI system, that failure is called reward misspecification or specification gaming.

In a state, it appears as dashboard success with citizen insolvency underneath it.

That is the shared shape.

It can raise aggregate GDP through population growth while failing to expand housing, infrastructure, public services, and capital per worker at the same pace.

It can raise tax receipts through inflation and bracket creep while real disposable income falls.

It can protect bank balance sheets by protecting collateral values, while making housing less affordable for new entrants.

It can support renters or buyers with demand-side subsidies while doing too little to expand supply, allowing part of the support to flow into higher rents or higher prices.

It can preserve the appearance of wealth by keeping asset prices high while younger households lose the ability to buy those assets.

The dashboard improves.

The citizen balance sheet deteriorates.

That is not prosperity. It is a measurement failure.

The Dashboard State does not have to hate citizens, intend harm, or require conspiracy. It only has to reward policies that improve headline continuity while failing to measure whether those same policies weaken household independence.

That is how the wrong objective function becomes dangerous.

This isn’t because GDP is useless, markets are bad, or confidence doesn’t matter. It is simply because a system eventually becomes what it measures, protects, and rewards.

If the dashboard measures GDP, tax receipts, asset values, population growth, debt rollover, and investor confidence, then policy will tend to protect those things.

If it does not also measure housing access, real wage power, savings resilience, ownership access, public-service capacity, family formation, and exit power, then those things can deteriorate while the official dashboard still says success.

This is why the model in this post does not begin with GDP.

It begins with the citizen.

The next step is to turn that divergence into a simple architecture: dashboard variables on one side, citizen-solvency variables on the other.


3. Citizen Solvency

Citizen solvency is the ability of a typical working-age household to participate in the economy without structural dependence.

It is not income.

A high wage does not create solvency if rent absorbs it. A growing economy does not create solvency if a household cannot reach ownership inside it. A rising stock market does not create solvency if the median household owns almost none of it.

Income is a flow.

Solvency is a position.

The number that matters is not only what a household earns, but what it has left after the essential costs of living. That remaining space — the discretionary surplus — is what becomes savings, what absorbs shocks, what enables a move, and what lets a person refuse a bad employer or a bad landlord without immediate collapse.

Citizen solvency therefore has several dimensions:

housing access
rent burden
ownership access
real wage power
savings resilience
debt-service burden
family-formation feasibility
public-service access
exit power

This is a starting point, not a finished list.

Some of these dimensions overlap. Housing access, rent burden, and ownership access are all different readings of the same underlying pressure: the cost and attainability of shelter. The model has to account for that relationship rather than triple-count it.

But the list has one advantage over GDP: every item asks whether the household can actually live inside the economy the state says is succeeding.

In the model, these dimensions become a Citizen Solvency Dashboard.

That dashboard is not a single magic number. Housing stress, wage power, savings resilience, debt burden, public-service access, and exit power are not the same quantity. Collapsing them too early would create false precision.

They should first be shown separately.

Then the model can ask whether they are moving together or diverging.

Together, they answer one question:

Is policy increasing household independence, or increasing dependency pressure?

The model does not begin with loaded historical analogies. It begins with measurable dependence: rent that cannot be escaped, debt that cannot be cleared, and the absence of any savings buffer that would let a household say no.

That is functional dependence.

And it is what the dashboard exists to detect before it hardens.

Each of these dimensions can be measured from public data. In the next section, they stop being a list and become the variables of a model you can run.


4. The Model in One Page

This post is not asking you to accept a metaphor.

It is proposing a test.

The test is simple: can the national dashboard and the citizen balance sheet move in opposite directions?

Put the two side by side.

The dashboard measures the continuity of the state:

GDP · tax receipts · asset prices · population growth
capital inflows · debt rollover · headline employment · bond-market confidence

The citizen solvency readout measures what the dashboard does not see directly:

housing access · rent burden · ownership access · real wage power
savings resilience · family-formation feasibility · public-service access · exit power

The model’s job is to watch for divergence between them.

When this rises And this happens The model calls it
Dashboard Citizen solvency rises Real growth
Dashboard Citizen solvency falls Dashboard growth
Debt Future capacity rises Productive transfer
Debt Future entry costs rise Regressive transfer
Population Capacity scales with it Scalable growth
Population Capacity does not scale Population-capacity illusion
Asset prices Ownership access rises Broad wealth formation
Asset prices Ownership access falls Entrant exclusion

That table is the model in plain English.

Everything else is the machinery that makes each row measurable.

Underneath both readouts sits the part policy actually acts on: the structural economy. This is the real system of rates, debt, wages, rents, house prices, housing supply, public-service capacity, productivity, tax revenue, and external constraints.

The dashboard and the citizen-solvency readout are measurements of what that system produces. They are not causes in their own right.

That distinction matters.

You cannot fix a country by editing its dashboard. You change the structural economy, then read the consequences off the citizen.

A fourth layer extends the same logic across time. The future citizen layer asks whether today’s policy improves or worsens the position of the next person trying to enter adult economic life.

Did today’s debt buy future capacity?

Or did it preserve current continuity while handing the next generation higher entry costs?

That question gets its own section later, because it is the intergenerational heart of the model. For now, the point is simpler: debt is not judged by whether it exists. It is judged by what it buys.

    flowchart TD
    P[Policy / Debt / Regulation] --> S[Structural Economy<br/>rates · wages · rents · housing supply · productivity · public services]
    S --> D[State Dashboard<br/>GDP · tax receipts · asset prices · debt rollover]
    S --> C[Citizen Solvency Readout<br/>housing access · savings · ownership · exit power]
    S --> F[Future Citizen Layer<br/>future capacity · future entry costs · future tax burden]
    D -. compared with .-> C
    C -. projected into .-> F
  

The model deliberately avoids collapsing these readouts into one magic number. Housing stress, wage power, savings resilience, public-service access, and exit power are different quantities. A single score would create false precision.

The first version should show the dimensions separately.

Then the model can ask where they move together, where they diverge, and which policy choices produce the divergence.

This matters because citizen solvency is also a chosen readout. It is better than GDP for the question this post asks, but it is not a magic escape from Goodhart’s law.

Any metric can become dangerous if it becomes the only target.

So the durable contribution is not merely replacing GDP with citizen solvency. It is building a system of readouts that can contradict each other: dashboard, citizen, future citizen, fiscal, external, and capacity.

The contradiction detector is the safety mechanism.

The AI layer appears later. For now, the important point is that the claim can be decomposed into variables, readouts, and falsification tests.

AI is useful here only insofar as it helps turn a vague complaint into an inspectable system: explicit variables, structural causes, measurement readouts, and data patterns that would weaken or break the thesis.

For example:

If dashboard gains consistently coincide with improving citizen solvency,
the thesis weakens.

If debt-funded capacity consistently lowers future entry costs,
the debt critique weakens.

If population growth is matched by housing, infrastructure, public services,
and capital deepening, then the population-capacity illusion is not present.

That is the contribution.

It offers inspection, not certainty.

The main post gives the readable model. The appendix gives the runnable one: the dynamic causal model, the variable ledger, the normalization choices, the scenario definitions, the sensitivity tests, and the code that regenerates the charts.

The claim and the machine that tests it should ship together.


5. The Population-Capacity Gap

The problem described here is not population, and it is not the people who arrive.

The problem is a gap.

A state can increase the number of people living, working, renting, consuming, paying tax, and transacting inside an economy. That will often make the aggregate dashboard look stronger. More people can mean more total consumption, more total rent payments, more total employment, more total tax receipts, and a larger headline GDP number.

But aggregate GDP is not what a household lives on.

A household lives inside the per-capita and per-household reality: income per person, housing per household, capital per worker, infrastructure per user, public-service capacity per citizen, and disposable surplus after essential costs.

That is the distinction the dashboard can miss.

Total output can rise while output per person stalls.

Total housing can rise while housing per household falls.

Total tax receipts can rise while service access per citizen deteriorates.

Total employment can rise while capital per worker weakens.

This reframes the issue away from people versus citizens, and toward demand versus capacity.

Population growth is one source of demand. So is household formation. So is income growth. So is credit expansion. So is investor demand for housing and land. The model does not ask whether more people arrived. It asks whether the capacity to absorb demand scaled with the demand.

The model therefore has to measure growth against capacity:

housing supply per household
public-service capacity per citizen
transport, water, and energy capacity per user
capital per worker
productivity per worker
construction throughput

If those measures rise with demand, population growth can be real growth. It can increase labour supply, tax receipts, entrepreneurship, cultural depth, productive energy, and national capacity.

But if demand rises faster than capacity, the dashboard can improve while the citizen balance sheet weakens.

When demand outruns capacity, the consequences are not mysterious. Rents rise because housing supply lags household formation. Public services strain because demand expands faster than delivery. Savings weaken because shelter and essentials absorb more disposable income. Ownership recedes because entry prices move faster than real wages.

The state reads the aggregate and calls it growth.

The citizen lives inside the gap and experiences it as pressure.

In the model, this becomes a capacity-gap test.

The old version of the formula would be too loose if it simply subtracted housing, infrastructure, public services, and capital from population growth as if they all had the same unit. They do not.

The safer version is to treat the capacity gap as a bottleneck problem, not a simple subtraction between unlike quantities.

Let \(G_t\) represent the capacity gap at time \(t\):

$$ G_t = D_t - C_t $$

Where \(D_t\) is demand pressure and \(C_t\) is the system’s absorptive capacity.

Demand pressure is a weighted combination of domestic strains:

$$ D_t = w_1 \Delta P_t + w_2 \Delta F_t + w_3 \Delta H_t + w_4 \Delta S_t + w_5 \Delta Q_t $$

Where:

\(ΔP_t\) = population growth
\(ΔF_t\) = household formation
\(ΔH_t\) = housing demand
\(ΔS_t\) = public-service demand
\(ΔQ_t\) = credit / income demand pressure

Capacity is constrained by the tightest bottleneck:

$$ C_t = \min \left( \Delta H^{comp}_t, \Delta I_t, \Delta S^{cap}_t, \Delta K^{worker}_t, \Delta B_t \right) $$

Where:

\(ΔH^{comp}_t\) = housing completions per household
\(ΔI_t\) = infrastructure capacity per user
\(ΔS^{cap}_t\) = public-service capacity per citizen
\(ΔK^{worker}_t\) = capital deepening per worker
\(ΔB_t\) = construction / administrative throughput

Capital Deepening versus Capital Dilution

This is also where the productivity story lives.

If labour supply rises while the physical, administrative, technological, and housing capital available per worker fails to rise with it, the economy can grow in aggregate while weakening the productive base beneath each worker.

That is the distinction between capital deepening and capital dilution.

Capital deepening means workers are matched by more tools, infrastructure, housing, systems, energy capacity, administrative throughput, and productive investment.

Capital dilution means more workers are added to a system whose supporting capacity has not scaled.

In simplified terms:

$$ \text{Capital Deepening}_t = \Delta \left(\frac{K}{L}\right)_t > 0 $$$$ \text{Capital Dilution}_t = \Delta \left(\frac{K}{L}\right)_t < 0 $$

Where \(K\) represents the capital stock broadly understood — housing, infrastructure, tools, productive systems, public capacity, and administrative throughput — and \(L\) represents labour.

This is not a claim about the workers.

It is a claim about the investment environment into which workers are added.

    flowchart TD
    P[Population growth] --> D[Demand pressure D_t]
    F[Household formation] --> D
    H[Housing demand] --> D
    Q[Credit / income demand shock] --> D

    HC[Housing completions] --> C[Absorptive capacity C_t]
    I[Infrastructure capacity] --> C
    S[Public-service capacity] --> C
    K[Capital per worker] --> C
    B[Construction / administrative throughput] --> C

    C --> G{Capacity gap G_t > 0?}
    D --> G

    G -->|Yes| X[Rent pressure · service strain · capital dilution · savings erosion]
    X --> Y[Citizen solvency falls]

    G -->|No| Z[Demand absorbed by capacity]
    Z --> W[Citizen solvency stable or improves]
  

This distinction matters because it prevents the model from blaming people for arriving.

The harm is not population itself.

The harm is population growth, or any demand growth, being used as a substitute for the housing, infrastructure, capital deepening, construction capacity, and productivity that would allow it to become lived solvency.

That is the population-capacity illusion: the dashboard celebrates aggregate volume while the citizen pays for the missing capacity.

The later Canada and Ireland sections apply this test directly.

The visual test is simple:

population growth / household formation
versus
housing completions / capacity expansion

The point is not the population line by itself.

The point is the gap.


6. The Future Citizen Balance Sheet

The present citizen balance sheet is only half the picture.

The other half is intergenerational.

Public debt is often described as borrowing from the future, as if that settled the matter. It does not. Borrowing from the future is not automatically wrong.

There is an old and orthodox case for borrowing: long-lived public capital can be funded across the generations that benefit from it. If a future citizen helps pay for a school, a grid, a rail line, a water system, a hospital, or a stock of public housing they actually use, the debt is not simply a burden. It is part of the cost of inherited capacity.

A future citizen may rationally inherit a liability if they also inherit the capacity that liability created.

Debt used to build homes, energy systems, transport, schools, water infrastructure, health capacity, productive industry, and real wage capacity can improve the future citizen balance sheet.

The future citizen inherits a bill.

But they also inherit useful capital.

That can be legitimate.

The problem is different when borrowing, guarantees, subsidies, or future tax commitments are used in ways that preserve present claims without building future capacity.

If policy stabilizes incumbent asset values, defends collateral, preserves income streams from scarce assets, or socializes downside risk, the intent does not have to be malicious for the incidence to change.

The present holder receives protection.

The future entrant receives part of the bill.

Worse, the future entrant may also face a higher cost of entry into the same asset system that policy helped preserve.

This is the concept this section turns on:

asymmetric incidence across time.

One operation has two sides.

The gain lands with current holders.

The cost lands with future entrants.

It is tempting to call this double-entry bookkeeping across generations, but the image is only useful if we notice where it breaks. In normal double-entry accounting, the entries balance inside one ledger. Here, the credit is posted to one cohort and the debit to another.

That is the asymmetry.

The future citizen balance sheet therefore has to show both sides: what the future inherits as capacity, and what the future inherits as burden.

Policy route Present effect What the future citizen inherits
Debt used to build housing, transport, energy, schools, water, or health capacity More capacity now Public capital, lower future pressure, improved entry conditions
Debt used to raise productivity and domestic productive capacity Slower payoff, but deeper productive base Higher wage capacity and stronger resilience
Debt or guarantees used to defend incumbent asset values and collateral Stabilized current balance sheets Debt service plus higher future entry price
Support for demand without matching supply Current relief or preserved income streams Higher rent or price baseline if capacity does not expand
Deferral of infrastructure and service investment Lower present political/fiscal pressure Public-capital backlog and weaker future service access

The same fiscal action can look similar on the state dashboard and produce opposite entries on the future citizen’s books.

That is why the question is not simply:

Can the state service the debt?

The question is:

What did the debt buy for the citizens who will service it?

If the answer is housing, infrastructure, energy capacity, public services, productivity, or lower future costs, the transfer may be productive.

If the answer is merely asset continuity, collateral protection, rent preservation, or another year of dashboard stability, the transfer may be regressive across generations.

The worst version occurs when future citizens are assigned the liabilities of a system that also makes their entry into adult life more expensive.

They do not merely inherit debt.

They inherit debt service plus higher house prices.

Debt service plus higher rents.

Debt service plus weaker public services.

Debt service plus reduced ownership access.

Debt service plus lower discretionary surplus.

That is why the model treats the future citizen position as a ledger, not a slogan:

Future Citizen Position — ledger, not scalar:

inherits as capacity:
    public capital
    housing stock
    productive capacity
    wage capacity
    ownership access
    lower future non-discretionary costs

inherits as burden:
    debt service
    tax burden
    rent baseline
    asset-entry cost
    public-service backlog
    reduced public-investment room
    lower discretionary surplus

This is not a literal accounting identity. The terms have different units, different timings, and different data sources. The ledger is a structured comparison, not a single number pretending to settle the argument.

There is also a discount-rate problem. Any attempt to collapse future costs and future benefits into a present value depends on how heavily the model discounts the future. A low discount rate makes future harms loom larger. A high market-style discount rate can make them appear smaller.

The model should not hide that choice.

It should expose it.

The technical appendix can make the discount rate adjustable and show how the conclusion changes when it moves.

None of this is invented from nothing. Economists already have tools for parts of the problem: generational accounting, fiscal incidence analysis, public-capital analysis, and the old distinction between borrowing for investment and borrowing for current consumption.

The contribution here is to connect those tools to citizen solvency.

The model does not ask only whether the state balance sheet survives.

It asks whether the next generation can own, save, form households, absorb shocks, access services, and build independent lives inside the economy they inherit.

That is the future citizen test.

Debt is legitimate when it buys future citizen capacity.

Debt becomes dangerous when it preserves present claims while raising the future citizen’s cost of entry.

The next section turns this ledger into a policy classifier.


7. What Did the Debt Buy?

Section 6 gave the principle: a fiscal action helps or harms the next generation depending on whether they inherit capacity or only the bill.

This section turns that principle into a test that can be run on a single policy.

A policy should not be judged only by its stated purpose.

It should be judged by where its benefits and burdens land.

That is the incidence rule, and it is what keeps the classifier honest. Almost every policy is described as protecting the economy, stabilizing the system, helping households, or supporting growth. The model does not begin with the press release. It begins with the ledger.

So every debt-funded or future-funded policy gets one question:

Did it raise future capacity at least as much as it raised future burden?

Operationally, that means:

Did the policy reduce future non-discretionary costs,
raise productive capacity,
improve ownership access,
or strengthen future wage power
enough to justify the debt service it leaves behind?

If yes, the debt may be investment.

If no, it is a transfer.

The model then classifies what kind.

Debt is used to… Where the burden and benefit land Class What would confirm it
Build housing, transport, energy, schools, water systems Future citizens inherit the asset and a lower cost of entry Productive Housing completions and public capital rise; future entry costs ease
Fund skills, health capacity, domestic industry, productivity Future citizens inherit stronger wage capacity and resilience Productive Productivity, real wages, and capacity per worker improve
Smooth a genuine temporary shock Burden and capacity roughly balance Neutral / stabilizing Productive capacity is preserved; no lasting entry-cost rise
Support demand without expanding supply Current relief, but higher future price or rent baseline Regressive Supports rise, supply stays flat, prices or rents rise
Support buyer demand without expanding housing supply Present buyers receive help; future entrants face higher prices Regressive House-price-to-income worsens; ownership access falls
Defend asset values or collateral without reform Current values are stabilized; entry cost is passed forward Regressive Asset values hold while younger ownership falls
Load liability and raise the cost of entry into the same assets Future citizens pay twice: debt service plus higher entry cost Extractive Debt-service burden rises and entry costs rise together

The labels can be debated.

They should be.

The important part is that the classifier does not stop at intention. It asks what the policy actually changed.

A productive transfer gives the future citizen something useful enough to justify the burden: housing, public capital, productive capacity, lower future living costs, stronger wage power, or better access to ownership.

A neutral or stabilizing transfer buys time during a genuine shock. It may not transform the future citizen balance sheet, but it prevents productive capacity from being destroyed.

A regressive transfer passes burden forward without enough matching capacity. It may help someone today, and it may even be necessary in a crisis, but if it does not expand supply or reduce future costs, it can leave the future citizen worse off.

An extractive transfer is the severe case: the future citizen inherits both the liability and a higher price of entry into the system the liability helped preserve.

This distinction matters because a state can always say it is acting responsibly.

A rent support may prevent immediate hardship.

A bank rescue may prevent wider damage.

A temporary subsidy may buy time.

But buying time is not the same as buying capacity.

The classifier therefore asks what happened after the intervention.

Did the state use the time to build homes, infrastructure, productive capacity, and lower future costs?

Or did it preserve the existing price structure and pass the liability forward?

That is where the Future Citizen Balance Sheet becomes operational.

It does not ask whether the policy sounded responsible.

It asks what future citizens received in exchange for the burden.

In the runnable version, the classifier does not produce a single truth score. It produces a structured comparison: a Citizen Solvency Dashboard, a capacity-gap reading, a future-citizen ledger, and a policy classification with the evidence behind it.

Representative inputs include:

GDP per capita
debt-service-to-revenue
house-price-to-income
rent-to-income
housing completions versus population growth
ownership by age cohort
median wage growth
public capital proxies
household savings

The full input and output spec belongs in the appendix and repository.

For now, the point is simple:

Policy claim:
    this protects the economy

Model question:
    what did it buy for the future citizen?

The classifier is only as good as the data it receives.

The next sections feed it real data: Canada, where the divergence is visible at scale, and Ireland, where the same test matters because the margin for error is smaller.


8. Canada: The Capacity Gap at Scale

Canada is the first place to run the model.

Canada is included not because it failed uniquely, but because it makes the population-capacity gap visible at scale.

For years, the Canadian dashboard could still look successful. Aggregate GDP grew. Population grew. Tax receipts rose. Asset values remained high. Major cities continued to look globally attractive. On the surface, the country looked like a large, stable, expanding advanced economy.

But the citizen balance sheet was telling a different story.

The useful question is not whether the economy became larger in total.

The useful question is whether the typical household became more solvent inside that larger economy.

That is where Canada matters.

Run the model from Section 5.

Population growth is demand growth. It can be good. It can support scale, enterprise formation, tax receipts, urban depth, labour supply, and long-term national capacity.

But only if capacity scales with it.

Housing must scale.

Infrastructure must scale.

Public services must scale.

Capital per worker must scale.

Productivity must scale.

Real wages must preserve access to shelter, savings, ownership, and family formation.

When those things move together, the classifier returns scalable growth.

When population rises but capacity lags, the capacity-gap flag fires.

Canada is useful because the relevant indicators can be tested directly.

The first test is the dashboard illusion.

Evidence requirement:
Compare aggregate GDP with GDP per capita over the high-population-growth period.

Likely sources:
Statistics Canada, OECD.

Model question:
Did the country grow in total while output per person stalled or fell?

If aggregate GDP rises while GDP per capita stagnates or declines, the dashboard and the citizen are no longer reading the same economy. The state can report expansion. The household can experience deterioration.

That is not a contradiction.

It is the model working.

The second test is the physical bottleneck.

Evidence requirement:
Compare population growth / household formation with housing completions.

Likely sources:
Statistics Canada, CMHC.

Model question:
Did the country add households faster than it added homes?

This is the core Canada chart.

The visual point is not immigration.

The visual point is the gap.

If demand rises faster than housing completions, the missing homes do not disappear from the system. They reappear as rent pressure, bidding pressure, longer commutes, crowding, delayed ownership, and reduced household surplus.

The third test is citizen solvency.

Evidence requirement:
Compare rent-to-income, house-price-to-income, real wages, savings resilience, and ownership access by age cohort.

Likely sources:
Statistics Canada, CMHC, OECD, Bank of Canada.

Model question:
Did asset values rise while entry access weakened?

This is where dashboard growth becomes citizen pressure.

A rising housing market can look like national wealth from one side of the ledger. It can support collateral values, bank balance sheets, household net worth for existing owners, and political confidence.

But for new entrants, the same price increase is not wealth.

It is a higher entrance fee.

That is why the model separates asset prices from ownership access. If asset prices rise while ownership access falls, the model does not call that broad wealth formation. It calls it entrant exclusion.

Canada also needs the productivity test.

Evidence requirement:
Track productivity, business investment, and capital per worker.

Likely sources:
Statistics Canada, OECD, Bank of Canada.

Model question:
Did the economy add workers faster than it added the capital, tools, infrastructure, and productivity required to raise output per worker?

This matters because population growth without capital deepening can preserve aggregate activity while weakening the per-worker foundation of prosperity.

That is the deeper failure mode.

The country becomes larger.

But the citizen’s claim on capacity becomes smaller.

As Section 5 argued, population is not the failure variable.

The gap is.

The model is making a narrower claim:

Population growth is a stress test on state capacity.

Canada chose, or allowed, rapid demand growth. The failure was that complementary capacity did not arrive at the same speed: homes, infrastructure, public services, productive capital, and wage capacity.

The state counted population as an asset.

But the citizen experienced unbuilt capacity as a liability.

That is the Canada lesson.

The failure was not too many people; it was too little capacity, and too much confidence that aggregate growth would automatically translate into household solvency.

In a runnable version of the model, Canada would not receive a slogan. It would receive a diagnostic readout.

Runnable model inputs:
    population growth
    household formation
    housing completions
    GDP growth
    GDP per capita growth
    rent burden
    house-price-to-income
    ownership by age cohort
    productivity growth
    capital per worker
    real wage growth
    household savings resilience

Runnable model outputs:
    capacity-gap reading
    citizen-solvency pressure map
    dashboard/citizen divergence flags
    policy classification
    evidence-backed charts

The result would not be a single truth score.

It would be a map of where the dashboard and the citizen diverged.

When the gap widens, the pressure usually appears first in rent.

Then in savings.

Then in ownership access.

Then in family formation.

Then in political trust.

By the time the dashboard admits failure, households have often been living inside the failure for years.

Canada is not included here to make the essay about Canada.

Canada is included because it makes the model concrete.

It shows how a state can grow in aggregate while the entrance price to adult life rises faster than the citizen’s capacity to pay it.

The next question is whether the same model travels.

Ireland is the harder test: a smaller economy, a more distorted GDP dashboard, less monetary room, and a housing constraint with less margin for error.

First Canada Run: Demand Versus Capacity

The Canada section should not remain only a description of what the model would look for. The table below gives the first empirical readout.

It maps the public-data indicators used in this section onto the model’s capacity-gap logic: dashboard growth, demand pressure, housing-capacity response, and the resulting flag.

The table does not claim to be the final Canada model. It is the first v0.1 application: enough to show whether the Canada case is merely rhetorical, or whether the population-capacity mechanism appears in the data.

Model test Public-data readout Calculation / comparison Model output
Demand pressure Household formation, 2024 482,000 net new households demand_pressure_input = high
Capacity response Net housing completions, 2024 276,000 net units capacity_response_input = constrained
Capacity gap Formation minus completions 206,000-unit gap; 1.75x demand-to-completion ratio canada_capacity_gap_flag = fired
Required build-rate gap Required vs projected annual completions to 2035 290,000 required vs 227,000 projected; 63,000/year shortfall (1.28x) canada_required_build_rate_gap_flag = fired
Citizen divergence Wage, rent, ownership, resilience series Not yet populated in the first run dashboard_citizen_divergence = provisional

Source: Parliamentary Budget Officer, Household Formation and the Housing Stock (2025). The capacity-gap flag fires because household formation ran at 1.75 times net completions, a 206,000-unit gap in a single year, and the required build rate to close it by 2035 sits 28% above the projected baseline. The dashboard/citizen divergence row stays provisional on purpose: confirming it requires the wage, rent, and ownership-by-cohort series, which the first run does not yet carry.


9. Ireland: The Small-State Stress Test

Ireland is not Canada.

That distinction matters.

Canada showed the population-capacity gap at scale: a large country, with its own currency and central bank, adding demand faster than it added housing, infrastructure, capital deepening, and productivity.

Ireland is a different test.

It is smaller. More open. More exposed. It sits inside the euro. It does not control its own currency. It cannot devalue its way out of a competitiveness shock. It cannot set monetary policy around Irish domestic conditions alone.

That gives Ireland less room for error.

But Ireland’s deeper problem is not only constraint.

It is measurement.

Headline GDP is unusually sensitive to multinational activity. That does not mean Ireland is fake. It does not mean the activity is imaginary. It means GDP is too volatile and too globally mediated to serve, by itself, as the readout for Irish citizen solvency.

In model terms, GDP is a valid input for some questions and a miscalibrated input for others. It can overstate domestic strength in one period and exaggerate apparent weakness in another.

If the question is:

How large is the activity booked through the Irish economy?

GDP matters.

But if the question is:

Can a typical Irish household build an independent life inside the Irish economy?

GDP is not enough.

The model therefore needs a measurement-correction layer.

For Ireland, the dashboard cannot be read directly. It has to be triangulated through domestic-capacity measures such as GNI*, modified domestic demand, domestic income, housing access, rent burden, ownership access, public-service capacity, and domestic productive depth.

That is why Ireland belongs in the model.

Ireland belongs in the model not because it is uniquely doomed, but because it is unusually revealing.

It shows what happens when the national dashboard looks strong before the citizen balance sheet has been properly measured.

The Irish dashboard can report extraordinary output, strong tax receipts, rising asset wealth, and high employment.

The model asks what those readouts mean after correction:

Which output is domestic capacity?

How concentrated are the receipts?

Who can enter the asset base?

Do wages buy independence after rent, tax, childcare, transport, and housing costs?

Those questions are not political slogans. They are feature-selection problems.

Irish GDP is not the citizen-solvency readout. It is one signal, to be compared with domestic measures that better approximate the lived economy.

Measurement correction:
    headline GDP
        compared with
    GNI*
    modified domestic demand
    domestic income
    real wage capacity
    housing access
    public-service capacity

If headline GDP rises while domestic citizen capacity does not deepen, the model raises a dashboard-divergence flag.

That is the first Irish test.

The second test is fiscal concentration.

Ireland’s corporation-tax receipts can make the state dashboard look safer than it really is. High receipts improve the fiscal picture. They can reduce borrowing pressure, fund public spending, support transfers, and create the appearance of national strength.

But if a large share of those receipts comes from a small number of multinational firms, the model cannot treat the revenue base as ordinary domestic capacity.

It has to flag concentration risk.

The danger is not that corporation tax is bad.

The danger is that recurring domestic obligations may be funded from concentrated, volatile, externally exposed receipts.

That creates a future-citizen risk.

If windfall receipts are converted into durable capacity — housing, infrastructure, energy systems, water systems, schools, healthcare capacity, transport, domestic productive capital — then the future citizen inherits assets.

If windfall receipts are consumed into permanent commitments without matching capacity, then the future citizen inherits the obligation without the asset.

That brings Ireland back to the question from Section 7:

What did the money buy?

Capacity?

Or continuity?

The third Irish test is citizen solvency.

A country can look rich while ordinary households face rising rents, delayed ownership, weak domestic capacity, and dependence on a narrow tax base.

This is not the same as saying Ireland is poor.

The claim is sharper:

Ireland can look rich while producing weaker future citizen solvency.

That is much harder to dismiss, because it does not deny the dashboard. It audits it.

The Irish question is:

Is Ireland becoming richer in a way that makes Irish citizens more independent, or richer in a way that makes them more dependent?

That is the whole test.

If GDP rises but GNI* and domestic capacity lag, the output is a measurement-distortion flag.

If corporation-tax receipts rise while concentration risk rises with them, the output is a fiscal-fragility flag.

If housing wealth rises while younger households are excluded from ownership, the output is an entrant-exclusion flag.

If population and employment rise while housing, infrastructure, and public services lag, the output is a capacity-gap flag.

If public spending rises but durable capacity does not, the policy is classified as continuity spending rather than productive transfer.

In a runnable version, Ireland becomes a different model instance from Canada.

Runnable model inputs:
    headline GDP
    GNI*
    modified domestic demand
    domestic income
    corporation-tax concentration
    windfall corporation-tax share
    recurring spending dependence
    housing completions
    rent burden
    house-price-to-income
    ownership access by age cohort
    domestic productivity
    domestic capital formation
    population growth
    public-service capacity proxies
    eurozone constraint flag

Runnable model outputs:
    GDP distortion flag
    fiscal-concentration risk flag
    domestic-capacity reading
    housing-access pressure map
    citizen-solvency divergence flag
    future-citizen liability risk
    external-constraint sensitivity

The output is not a verdict — Ireland good, Ireland bad, Ireland doomed.

The output is a diagnostic map.

It shows where the dashboard is reliable, where it needs correction, and where apparent national strength may not translate into citizen solvency.

That is why the section needs at least three evidence hooks.

Figure requirement:
    headline GDP versus GNI* / modified domestic demand

Visual point:
    the headline dashboard is not the same as domestic citizen capacity.
Figure or table requirement:
    corporation-tax concentration / windfall corporation-tax share

Visual point:
    fiscal strength can hide concentration risk.
Figure requirement:
    house-price-to-income, rent burden, or ownership access by age cohort

Visual point:
    asset wealth and citizen access can move in opposite directions.

The mistake would be to claim that Ireland is poor, or conversely, that Irish GDP is entirely fake. Neither is true.

That is not the claim either.

The claim is that Ireland’s dashboard needs correction before it can be trusted as a measure of citizen solvency. If multinational activity surges, GDP can make the economy look stronger than the domestic citizen readout. If multinational activity reverses, GDP can make the economy look weaker than the domestic citizen readout. The problem is not the direction of the distortion. The problem is treating the distorted measure as the citizen readout.

And the model must remain falsifiable.

If Ireland converts concentrated fiscal windfalls into durable public capital, builds enough housing, deepens domestic productive capacity, improves infrastructure, and lowers the entrance cost to adult life, then the citizen-solvency readout should improve.

The readout should show that.

It is not a doom machine.

It is an audit layer.

Ireland therefore exposes the next part of the model: the external constraint. A state can want to improve citizen solvency and still face limits set by currency regimes, global capital, multinational tax structures, energy dependence, import exposure, bond markets, and European institutions.

That is where the model has to go next.

First Ireland Run: Dashboard Versus Domestic Readout

The Ireland section also needs a first empirical readout.

Ireland is not used here as another Canada. It tests a different part of the model: whether headline GDP can diverge from domestic-capacity measures, and whether fiscal strength can hide concentration risk.

The table below maps the public-data indicators onto the Ireland model instance. GDP/GNI* divergence does not prove citizen poverty. It proves measurement distortion. Citizen pressure has to be read separately through household-side indicators such as income, deprivation, rent burden, ownership access, or savings resilience.

Model test Public-data readout Calculation / comparison Model output
GDP vs domestic demand GDP growth vs Modified Domestic Demand, Q1 2026 -12.1% vs +0.6%; 12.7pp gap ireland_gdp_mdd_divergence_flag = fired
GDP vs GNP GDP growth vs GNP growth, Q1 2026 -12.1% vs +1.5%; 13.6pp gap ireland_gdp_gnp_divergence_flag = fired
Multinational vs domestic MNE-dominated vs domestic sectors, Q1 2026 -27.1% vs +0.4%; 27.5pp gap ireland_multinational_domestic_sector_divergence_flag = fired
Fiscal concentration Top three groups’ share of corporation tax, 2024 46% (about €13bn) ireland_fiscal_concentration_flag = fired
Citizen pressure Household-side indicators (SILC, rent, income) Not inferred from GDP ireland_dashboard_citizen_divergence_flag = not assessed

Sources: CSO, Quarterly National Accounts Q1 2026 (Provisional); IFAC corporation-tax concentration analysis (2024). Note the direction: in this quarter headline GDP fell 12.1% while domestic demand rose, so GDP is understating domestic conditions, not exaggerating them. That is the “distortion in either direction” point made concrete. A single quarter this extreme is itself the argument: GDP swings like this precisely because multinational accounting moves it, which is why it cannot serve as the citizen readout. The load-bearing evidence here is not the one quarter but the structural figure beneath it: the 46% corporation-tax concentration and the persistent gap between GDP and domestic measures. The citizen-pressure row is deliberately not assessed: GDP/GNI* and sector divergence prove measurement distortion and fiscal-concentration risk, not household poverty, which has to be read separately from SILC, rent, and income data.


10. The External Constraint

The model now leaves the closed domestic system.

No small open economy operates in isolation. A state can control parts of its housing policy, tax policy, planning system, public investment, infrastructure delivery, and domestic regulation. But it does not set every condition under which those choices are made.

It does not set the global cost of capital, the dollar system, the European monetary regime, global energy prices, China’s industrial cost structure, or global tax rules by itself.

A serious model has to include those constraints without exaggerating them.

The state still has agency.

But agency operates inside boundary conditions.

That is the external-constraint layer.

In the model, external constraints are not excuses. They are exogenous inputs: variables the state does not fully control, but which shape its domestic policy room.

external constraint
    -> domestic policy room
    -> capacity delivery
    -> citizen solvency

That middle layer matters.

External forces do not automatically determine citizen outcomes. They change the price, feasibility, timing, and risk of domestic choices. The state still decides whether to build housing, reform planning, invest in public capital, deepen productivity, diversify its tax base, and protect the future citizen balance sheet.

But it makes those decisions inside a system.

The United States matters because it shapes the global financial environment. The dollar system, Treasury market, equity-market depth, dollar liquidity, and safe-asset demand influence the benchmark conditions under which other states borrow, invest, and compete for capital.

That is the rate channel.

When global rates rise, smaller states do not set the benchmark. They absorb it through sovereign borrowing costs, mortgage rates, investment hurdles, asset valuations, and fiscal pressure.

There is also an allocation channel.

The United States can absorb global capital at a scale few countries can match. Its financial markets offer depth, liquidity, collateral, and perceived safety. When global savings are pulled toward US assets, other states compete against that benchmark for investment.

That does not require a conspiracy.

It is a structural feature of the system.

The US can run a financial model that smaller states cannot easily copy, because the world still treats US financial assets as core collateral. That gives the US more room to roll debt, sustain asset valuations, absorb global capital, and tolerate fiscal stress for longer.

Countries outside that centre face a different constraint: they compete for capital, absorb global rates, and are compared against deeper, more liquid markets.

That is the financial gravity well.

China matters differently.

It is not primarily a financial gravity well in this model. It is an industrial pressure input: manufacturing scale, export capacity, state coordination, cost structure, and tradable-sector pressure.

For Ireland, this channel should not be overstated. Ireland is not Germany. But any small Western economy trying to rebuild domestic production does so in a world where China can manufacture at scale and often at prices domestic firms struggle to match.

That does not make reindustrialisation impossible.

It makes it harder.

The eurozone matters more directly for Ireland.

Ireland does not control its own currency. It does not set monetary policy for Irish domestic conditions alone. The ECB rate becomes an external input into Irish mortgages, credit conditions, sovereign financing, business investment, and housing affordability.

That does not mean Ireland has no choices.

It means Ireland has fewer release valves.

A country with its own currency can, in some circumstances, absorb pressure through exchange-rate movement, domestic monetary policy, or inflation. Ireland’s adjustment channels are narrower. Pressure is more likely to move through wages, prices, fiscal policy, housing, public services, emigration, and dependence on European institutions.

EU fiscal rules matter for the same reason.

If the model recommends public investment to build capacity, it must also ask whether the state has legal, fiscal, and market room to borrow and spend. A capacity-building plan is not only an economic idea. It is a policy-room problem.

Energy and import dependence also matter.

A small open economy is often a price-taker for energy, construction inputs, food, machinery, technology, and intermediate goods. When external prices rise, the effect is not abstract. It enters the citizen balance sheet through heating, electricity, transport, rent, food, construction costs, and mortgage affordability.

Global tax rules matter too.

Ireland’s corporation-tax position is not set entirely in Ireland. International tax agreements, US policy, EU rules, profit-shifting changes, and global minimum-tax frameworks can change the value of the tax base Ireland has come to depend on.

That is why multinational dependency appears in the external-constraint layer as well as the Ireland section.

It affects GDP measurement.

It affects tax receipts.

It affects fiscal risk.

It affects the future citizen balance sheet if recurring domestic obligations are funded from concentrated, externally exposed receipts.

The model therefore treats the external world as a constraint vector.

US financial gravity:
    affects benchmark rates, dollar liquidity, capital flows,
    asset valuations, and fiscal stress

Eurozone monetary regime:
    affects Irish interest rates, mortgage costs,
    credit conditions, and adjustment channels

EU fiscal rules:
    affect public-investment room and capacity-building options

Energy and import exposure:
    affects household costs, construction costs,
    inflation, and domestic productive capacity

China industrial pressure:
    affects tradable sectors, import prices,
    and domestic production options

Multinational dependency:
    affects Irish tax revenue, GDP measurement,
    and fiscal fragility

Global tax rules:
    affect the durability of corporation-tax receipts
    and the stability of the fiscal dashboard

This keeps the argument measured.

The housing crisis is still primarily a domestic capacity failure.

Planning, supply, land, credit, public housing, infrastructure, construction throughput, and policy design matter enormously.

Global forces do not replace that story.

They amplify it.

External rates can worsen mortgage affordability. Import prices can raise construction costs. Capital flows can affect asset valuations. EU fiscal rules can shape the investment envelope. But homes are still permitted, financed, serviced, connected, and built through domestic systems.

That is the agency layer.

A state cannot set every external variable.

But it can decide whether to convert available resources into capacity.

It can decide whether windfall receipts become durable assets.

It can decide whether planning systems increase throughput or preserve scarcity.

It can decide whether public capital lowers future entry costs.

It can decide whether debt buys capacity or continuity.

So the point is not that Ireland has no choices.

The point is that choices made inside a dependency structure are still choices, but their cost, timing, and feasibility are constrained.

    flowchart TD
    A[US financial conditions] --> B[Global rates and capital flows]
    C[China industrial pressure] --> D[Tradable-sector and import-price pressure]
    E[Eurozone monetary regime] --> F[Limited monetary adjustment]
    G[Multinational dependency] --> H[Tax and GDP exposure]
    I[Energy and import exposure] --> J[Imported cost pressure]
    K[EU fiscal rules] --> L[Public-investment headroom]

    B --> M[Domestic policy room]
    D --> M
    F --> M
    H --> M
    J --> M
    L --> M

    M --> N[Capacity delivery]
    N --> O[Housing, infrastructure, public services, productivity]
    O --> P[Citizen Solvency Readout]

    P --> Q[Exit pressure / emigration / trust loss]
    Q --> M
  

The earlier diagrams show how policy affects the economy and how the economy is read by the dashboard and the citizen. But a society is not a one-way machine. Citizen solvency feeds back into politics.

When households lose independence, the pressure does not remain private. It becomes voting behaviour, protest, distrust, emigration, institutional stress, and eventually a demand to change the objective function itself.

This is what makes the state an adaptive optimizer rather than a static machine.

If the dashboard is rewarded for long enough, the groups that benefit from dashboard preservation gain incentives to defend it. Asset holders defend asset prices. Institutions defend continuity. Fiscal systems defend the revenue base. Political actors defend the numbers that make them look competent.

The proxy does not merely measure the system.

It begins to organize power around itself.

    flowchart TD
    P["🏛️ Policy / Debt / Regulation"]
    S["⚙️ Structural Economy<br/>rates · wages · rents · housing · productivity · services"]
    D["📊 State Dashboard<br/>GDP · tax receipts · asset prices · debt rollover"]
    C["🧍 Citizen Solvency<br/>housing access · savings · exit power · family formation"]
    F["👶 Future Citizen Balance Sheet<br/>future capacity · future entry costs · future tax burden"]
    T["🗳️ Political Feedback<br/>voting · protest · trust · emigration · legitimacy"]
    O["🎯 Objective Function Update<br/>dashboard preservation ↔ citizen solvency"]

    P --> S
    S --> D
    S --> C
    S --> F

    D -. "official success signal" .-> O
    C -. "lived reality signal" .-> T
    F -. "intergenerational pressure" .-> T

    T --> O
    O --> P

    C -->|"dependency pressure rises"| T
    T -->|"policy demand changes"| P

    classDef policy fill:#e8f0ff,stroke:#2f5fd0,stroke-width:2px,color:#111;
    classDef structure fill:#fff4df,stroke:#d99000,stroke-width:2px,color:#111;
    classDef dashboard fill:#e8ffe8,stroke:#2d9b45,stroke-width:2px,color:#111;
    classDef citizen fill:#ffe8e8,stroke:#d04a4a,stroke-width:2px,color:#111;
    classDef future fill:#f2e8ff,stroke:#7a4fd0,stroke-width:2px,color:#111;
    classDef politics fill:#fff0fa,stroke:#c13f91,stroke-width:2px,color:#111;
    classDef objective fill:#f7f7f7,stroke:#555,stroke-width:3px,color:#111;

    class P policy;
    class S structure;
    class D dashboard;
    class C citizen;
    class F future;
    class T politics;
    class O objective;
  

In a runnable version, this layer does not produce a moral verdict. It produces a sensitivity map.

External constraint inputs:
    US benchmark rates
    dollar liquidity conditions
    ECB policy rate
    eurozone fiscal constraints
    sovereign spread / borrowing cost
    energy and import exposure
    construction-input cost pressure
    multinational tax exposure
    global tax-rule sensitivity
    tradable-sector pressure

External constraint outputs:
    policy-room score
    fiscal-sensitivity flag
    imported-cost pressure
    capital-flow pressure
    tax-base exposure flag
    external-dependency map
    citizen-solvency shock sensitivity

That is what keeps the model honest.

It does not let the state blame everything on the outside world.

It also does not pretend the outside world is irrelevant.

It asks a narrower, more useful question:

Given the external constraint,
what domestic capacity can still be built,
and what happens to the citizen balance sheet if it is not?

At this point, the model has become multidimensional.

It includes dashboard metrics, citizen solvency, housing capacity, debt classification, the future citizen balance sheet, country-specific measurement correction, fiscal concentration, and external constraints.

Ordinary political argument cannot police all of those moving parts reliably.

That is where the AI audit layer enters.


11. The AI Audit Layer

At this point, the post could become a normal macro essay.

That would not be enough.

The model now has too many moving parts for prose alone to audit reliably: dashboard metrics, citizen-solvency variables, housing capacity, debt classification, future-citizen burdens, country-specific measurement corrections, fiscal concentration, external constraints, feedback loops, and time lags.

A normal essay can gesture at those relationships.

A model has to expose them.

That is where the AI layer enters.

The AI layer is not an oracle, a prophecy, or a mechanism to prove the author right. Its narrower job is to make the argument inspectable.

It helps turn prose into structure.

It helps turn structure into tests.

It helps turn vague claims into things a reader can challenge.

In this model, AI is used for seven tasks:

Some of this is AI.

Some of it is ordinary software.

The AI is useful for the linguistic and adversarial work: extracting hidden claims from prose, identifying ambiguous terms, proposing variables, finding contradictions, generating counterarguments, and checking whether the model has smuggled a conclusion into its own definitions.

The software is useful for the mechanical work: storing variables, running scenarios, comparing series, calculating subindices, checking thresholds, and producing repeatable reports.

The point is not to pretend that AI discovers truth.

The point is to stop language from hiding claims.

Macroeconomic arguments often hide inside words.

Words like:

growth
prosperity
competitiveness
sustainability
affordability
resilience
reform
investment
prudence
capacity

can mean almost anything unless they are forced into structure.

The model asks:

What variable does this word refer to?

What data source would measure it?

What direction should it move?

What lag should we expect?

What would prove the claim wrong?

Take affordability.

In prose, affordability can mean a feeling, a political complaint, a mortgage rule, a rent burden, a deposit barrier, or a house-price-to-income ratio.

The model has to split it apart:

affordability:
    rent_to_income
    house_price_to_income
    deposit_years_required
    mortgage_payment_to_income
    ownership_access_by_age_cohort
    residual_income_after_non_discretionary_costs

Once that happens, two people arguing about “affordability” may discover they were not arguing about the same thing.

One was talking about rent.

One was talking about deposits.

One was talking about mortgage payments.

One was talking about ownership access for younger households.

That is one AI contribution: not final judgement, but decomposition.

There is a second contribution, which is easier to miss.

AI can act as a lens-finder.

It can surface the frame that a domain specialist may not reach because the specialist is already inside the domain’s native vocabulary. In this post, the useful frame is not only fiscal. It is also an objective-function problem.

The state has a dashboard.

The dashboard becomes a proxy.

The proxy becomes the target.

The target diverges from the human layer.

That is why the economics, the systems language, and the AI-alignment language meet here.

The AI did not prove the frame.

It helped locate it.

The model, the data, the falsification tests, and the reader still have to decide whether the frame earns its keep.

The first methodological correction is that this is not a static DAG.

A true DAG is acyclic. But an economy contains feedback loops.

Rent affects savings.

Savings affect ownership access.

Ownership access affects household formation.

Household formation affects housing demand.

Housing demand affects rent.

If those are drawn inside one static graph, the model becomes circular.

The cleaner representation is a time-indexed causal graph.

Within a single time slice, the graph can remain acyclic.

Across time steps, feedback appears.

Rent_t
  → Savings_t
  → OwnershipAccess_t+1
  → HouseholdFormation_t+1
  → HousingDemand_t+2
  → Rent_t+2

The same is true for asset prices and credit:

AssetPrices_t
  → Collateral_t
  → CreditAvailability_t+1
  → HousingDemand_t+1
  → AssetPrices_t+2

And for fiscal pressure:

InterestCost_t
  → FiscalRoom_t+1
  → PublicInvestment_t+1
  → Capacity_t+2
  → RevenueBase_t+3
  → InterestStress_t+3

Ireland adds another loop:

RentBurden_t
  → SavingsPressure_t
  → ExitPressure_t+1
  → Emigration_t+1
  → LabourSupply_t+2
  → TaxBase_t+2
  → PublicServiceCapacity_t+3

This matters because it stops the model from pretending the economy is simpler than it is.

It also forces us to specify lags.

Housing supply does not respond instantly.

Debt interest does not reset instantly.

Productivity does not appear instantly.

Tax receipts do not always respond instantly.

Migration responds to conditions, but also changes conditions.

The model needs time.

The second methodological correction is that the structural layer and the measurement layer must be separate.

The model cannot draw an arrow from rent to the Citizen Solvency Dashboard and pretend that is causal if rent is one of the dashboard’s components.

That is measurement, not causation.

So the model has two layers.

Structural layer:
    rates
    debt
    wages
    rent
    house prices
    housing supply
    population-capacity gap
    productivity
    tax revenue
    public services
    external constraints

Measurement / readout layer:
    Citizen Solvency Dashboard
    Future Citizen Balance Sheet

The structural layer describes the economy.

The measurement layer reads the state of the citizen.

Keeping those layers separate prevents circularity and makes the model inspectable. Edges, lags, weights, data sources, normalization choices, thresholds, and scenarios become explicit surfaces for review rather than hidden assumptions inside prose.

That is the standard the model has to meet.

A model that cannot be attacked cannot be trusted.

The AI audit layer therefore sits beside the model, not inside it.

It is a sidecar.

It checks the claims, the variables, the edges, the lags, the sources, the normalization, and the falsification tests.

It does not become the economy.

It audits the representation.

ai_audit_layer:
  role: audit_not_oracle

  tasks:
    - decompose_claims_into_variables
    - separate_structural_nodes_from_readouts
    - map_claims_to_data_requirements
    - generate_scenario_matrix
    - run_sensitivity_checks
    - generate_falsification_tests
    - generate_adversarial_counterclaims

  constraints:
    - no_oracle_claims
    - no_unsourced_outputs
    - expose_assumptions
    - preserve_uncertainty
    - separate_model_outputs_from_policy_values
    - flag_missing_data
    - flag_circularity
    - flag_false_precision

The AI layer also creates risks: hallucination, source laundering, false precision, overfitting, and political judgement disguised as technical scoring.

That is why every output must be bound to one of four things:

a source
an assumption
a test
a declared uncertainty

An edge requires a mechanism.

A weight requires evidence.

A conclusion requires a falsification test.

If none can be supplied, the claim is marked as speculative.

That is the discipline.

The AI does not remove judgement; it makes it explicit.

With the audit layer defined, the next step is to define the model itself: the inputs, subindices, transformations, classifications, outputs, and evidence hooks that make up the Citizen Solvency Model.


12. The Citizen Solvency Model

The Citizen Solvency Model is built to test two divergences.

The first is immediate:

Can the national dashboard improve while current citizen solvency deteriorates?

The second is intergenerational:

Can current state continuity be maintained by weakening the future citizen balance sheet?

Those are the two core diagnostics.

The first catches dashboard growth.

The second catches asymmetric transfer across time.

A state can look stable now by passing higher entry costs, weaker public capacity, and larger liabilities to the next person trying to enter adult economic life.

That is why the model has to read both the present citizen and the future citizen.

The dashboard variables are familiar:

GDP
tax receipts
asset prices
debt rollover
population growth
capital inflows
headline employment
bond-market confidence
debt / GDP
interest / revenue

These are the numbers modern states already watch closely. They matter. A state cannot ignore growth, tax receipts, debt service, investment, employment, or financial stability.

But they are not the citizen balance sheet.

There is also a bridge variable:

GDP per capita

GDP per capita is useful because it decomposes aggregate growth. If total GDP rises but GDP per person stalls or falls, the model has found an early dashboard/citizen divergence. But GDP per capita is still not citizen solvency. It does not tell us whether the median household can pay rent, build savings, access ownership, or absorb shocks.

The citizen variables are different.

They include shelter, income power, resilience, services, and exit capacity.

shelter / access:
    rent burden
    house-price-to-income
    time-to-deposit
    ownership access
    housing availability

income / resilience:
    real wage power
    residual discretionary income
    savings resilience
    household debt-service exposure
    non-discretionary cost stack

family / services:
    family-formation feasibility
    childcare cost burden
    public-service capacity
    healthcare / education access

exit / autonomy:
    exit power
    ability to leave a bad landlord
    ability to leave a bad employer
    ability to move without insolvency

External dependency is not a citizen variable. It belongs in the external constraint layer: eurozone rules, global rates, multinational tax exposure, energy/import dependence, and global capital conditions.

The model asks whether the dashboard variables and citizen variables move together or apart.

If GDP rises and citizen solvency rises, the model classifies that as real growth.

If GDP rises while rent burden, entry costs, service strain, debt exposure, and household fragility rise with it, the model classifies that as dashboard growth.

The purpose of the model is not to prove doom, but to distinguish between policy paths.

Model Architecture

The model has architecture layers and readouts.

Those are not the same thing.

The architecture is:

Structural layer:
    rates
    debt
    wages
    rents
    house prices
    housing supply
    productivity
    tax revenue
    public services
    infrastructure
    administrative throughput

External constraint layer:
    global rates
    eurozone regime
    EU fiscal rules
    capital flows
    energy/import exposure
    multinational tax exposure
    global tax rules
    tradable-sector pressure

Measurement / readout layer:
    Citizen Solvency Dashboard
    Future Citizen Balance Sheet

Policy classifier:
    productive / stabilizing / regressive / extractive

AI audit sidecar:
    checks assumptions
    checks data sources
    checks lags
    checks weights
    checks normalization
    checks scenarios
    generates falsification tests

The readouts are:

Current Citizen Solvency Dashboard
Future Citizen Balance Sheet
capacity-gap flags
dashboard/citizen divergence flags
policy classification
dependency pressure

The architecture describes how the model is built.

The readouts describe what the model emits.

Schema Preview

The full model belongs in a technical appendix or repo.

But the main post needs enough schema to make the claim inspectable.

citizen_solvency_model:
  version: "csm.v0.1"

  diagnostics:
    dashboard_vs_current_citizen:
      question: "Can the national dashboard improve while current citizen solvency deteriorates?"
      outputs:
        - real_growth
        - dashboard_growth
        - citizen_solvency_divergence

    current_continuity_vs_future_citizen:
      question: "Can current continuity be maintained by weakening the future citizen balance sheet?"
      outputs:
        - productive_transfer
        - stabilizing_transfer
        - regressive_transfer
        - extractive_transfer

  inputs:
    dashboard:
      growth:
        - gdp_growth
        - gdp_per_capita_growth
        - headline_employment
      fiscal:
        - tax_receipts
        - debt_to_gdp
        - interest_to_revenue
        - debt_rollover
      financial:
        - asset_prices
        - capital_inflows
        - credit_growth
        - bond_market_confidence

    citizen:
      shelter_access:
        - rent_burden
        - house_price_to_income
        - time_to_deposit
        - ownership_access
        - housing_availability
      income_resilience:
        - real_wage_power
        - residual_discretionary_income
        - savings_resilience
        - household_debt_service_exposure
        - non_discretionary_cost_stack
      public_capacity:
        - public_service_capacity
        - childcare_cost_burden
        - healthcare_access
        - education_access
      autonomy:
        - exit_power

    future_citizen:
      - future_debt_service
      - future_tax_burden
      - future_housing_entry_cost
      - future_rent_burden
      - public_capital_stock
      - productive_capacity
      - wage_capacity
      - future_ownership_access

    external_constraints:
      - global_rates
      - eurozone_constraint
      - eu_fiscal_rules
      - energy_import_exposure
      - multinational_tax_exposure
      - global_tax_rule_sensitivity

  architecture:
    - structural_layer
    - external_constraint_layer
    - measurement_readout_layer
    - policy_classifier
    - ai_audit_sidecar

  outputs:
    - current_citizen_solvency_dashboard
    - future_citizen_balance_sheet
    - capacity_gap_flags
    - dashboard_citizen_divergence_flags
    - policy_classification
    - dependency_pressure

That schema is not the final model.

It is the minimum inspectable version.

A reader should be able to ask:

Where did this input come from?
How was it normalized?
What weight did it receive?
What threshold triggered the flag?
What would falsify the classification?

If those questions cannot be answered, the model is not ready.

Policy Classifier

The policy classifier asks the simplest question in the whole framework:

What did this policy buy?

Did it buy capacity?

Or did it buy continuity?

That distinction is the model’s normative and technical centre.

The classifier is not judging intent.

It is judging incidence.

If the benefit lands with current holders while the burden lands with future entrants, the model flags asymmetric incidence across generations. This is the Future Citizen Balance Sheet from Section 6 expressed as a classifier rule.

The model uses four policy classes.

Productive transfer:
    future citizens inherit some burden,
    but also inherit greater capacity.

Stabilizing transfer:
    future burden is used to preserve productive capacity
    through a genuine temporary shock.

Regressive transfer:
    future citizens inherit burden
    without a matching capacity gain.

Extractive transfer:
    future citizens inherit burden,
    weaker solvency,
    and higher entry costs into assets
    whose current values were protected.

The final category is the most severe and should be used carefully.

It does not require proving malicious intent.

It describes incidence.

If a policy raises public liabilities, preserves current asset values, and worsens the future citizen’s cost of entering housing or ownership, then the benefit and burden are split across time.

The benefit goes to current holders.

The burden goes to future entrants.

That is asymmetric incidence across generations.

Contradiction Detector

The most important output is not a forecast.

It is a contradiction detector.

If dashboard ↑ and citizen solvency ↑:
    real growth

If dashboard ↑ and citizen solvency ↓:
    dashboard growth with citizen-solvency divergence

If debt ↑ and future capacity ↑:
    productive transfer

If debt ↑ and productive capacity is preserved through a temporary shock:
    stabilizing transfer

If debt ↑ and future capacity does not rise:
    regressive transfer

If debt ↑ and future entry costs ↑:
    extractive transfer

If population ↑ and capacity scales with it:
    scalable growth

If population ↑ and capacity fails to scale:
    population-capacity illusion

If asset prices ↑ and ownership access ↑:
    broad wealth formation

If asset prices ↑ and ownership access ↓:
    asset appreciation with entrant exclusion

That is the point.

The model does not reject growth, debt, population growth, markets, or asset ownership.

It asks whether each one improves citizen solvency or creates dependency pressure.

Scenario Tests

The model tests scenarios, not slogans.

The first scenario is demand growth without capacity growth.

demand rises
GDP rises
tax receipts rise
housing demand rises
but housing supply fails to scale
rent burden rises
savings resilience falls
ownership access falls
citizen solvency falls
dependency pressure rises

This is not a prediction.

It is a mechanism.

The question is whether the data show the mechanism operating in a given country.

Population growth is not treated as harmful by itself. It becomes a solvency risk only when housing, infrastructure, public services, capital deepening, and productivity fail to scale with it.

If population rises and capacity rises with it, the classifier returns scalable growth.

If population rises and capacity does not rise with it, the capacity-gap flag fires and the pressure appears in household variables: rent burden, savings resilience, ownership access, service strain, and dependency pressure.

The second scenario is demand-side housing support without supply.

state supports buyers or renters
headline affordability may improve temporarily
but if supply is fixed,
support can capitalise into prices or rents
future entry cost rises
future citizen balance sheet worsens

This is not a claim that every subsidy is bad. Emergency rent support may be necessary, and buyer support paired with major supply expansion may behave differently.

The model asks:

Did the policy change capacity, or did it change price?

The third scenario is productive investment.

state borrows
but builds housing, energy, transport, water, schools, health capacity,
or domestic productive capacity
future debt service rises
but future citizen capacity also rises

This can improve the Future Citizen Balance Sheet.

The debt is real.

The interest cost is real.

But the future citizen receives something in return.

The fourth scenario is external fiscal shock.

corporation-tax receipts fall
interest rates reset higher
asset-sensitive receipts weaken
multinational activity shifts
external capital conditions tighten

The question then becomes:

Does the state absorb the shock by improving productivity and capacity, or by pushing more pressure onto households?

That is where citizen solvency becomes a stress test.

A state can respond to fiscal pressure by building future capacity.

Or it can protect the dashboard by weakening the citizen balance sheet.

The fifth scenario is AI productivity upside.

AI deployment raises productivity
real wage power rises
service delivery improves
construction throughput improves
public administration becomes faster
non-discretionary costs fall or stabilise
citizen solvency rises
future citizen capacity rises

This is the optimistic falsification case.

If AI produces broad-based productivity gains, lowers delivery costs, improves public services, and raises real wage power faster than entry costs rise, the classifier returns productive capacity expansion.

The thesis is not that the citizen must lose.

The thesis is that dashboard improvement is not enough.

The citizen readout has to improve too.

Scenario Comparison

The same dashboard movement can hide different citizen outcomes.

The Technical Appendix

The main post should not carry the full machinery.

The appendix should: data dictionary, source registry, structural edge ledger, measurement-layer mapping, country configuration files, normalization choices, weighting choices, scenario definitions, falsification registry, sensitivity analysis, discount-rate tests, YAML scenario configs, and Python chart-generation scripts.

The first version of the model will not be perfect.

That is fine.

The important thing is that it is honest: every assumption is visible, every edge can be challenged, every output can be rerun, and every citizen-first claim has to pass through measurable structure.

That is what AI is useful for here.

Not certainty.

Inspection.

The classifier can tell us when debt bought capacity and when it bought continuity.

But it also raises a harder question: what happens when the existing repayment path itself starts to damage future citizen solvency?

That is the boundary condition for restructuring.


13. When the State Restructures the Citizen

A debt system can fail before it defaults.

That is the uncomfortable point.

Debt is usually discussed as a problem for the state.

Can the state borrow, refinance, keep bond markets calm, service the interest, and avoid default?

Those are important questions.

But they are not the only questions.

Citizen-solvency analysis asks a different question:

What is being damaged so the state can keep paying?

A state can continue servicing debt while weakening the citizens who must ultimately support that debt.

It can raise taxes, cut services, delay infrastructure, suppress real wages, protect asset prices, sell public assets, and allow housing costs to rise.

It can preserve the state balance sheet by weakening the citizen balance sheet.

So debt sustainability cannot mean only:

Can the state keep paying?

It must also mean:

Can the state keep paying without destroying the future citizen base that makes repayment meaningful?

This is where restructuring enters the model, not as a recommendation but as a boundary condition.

Formal sovereign restructuring is costly, dangerous, legally complex, institutionally constrained, and usually a sign that earlier policy paths have already failed.

For a eurozone state such as Ireland, it may not be an ordinary domestic policy lever at all.

That constraint does not remove the adjustment.

It changes where the adjustment lands.

It may route through taxes, weaker services, delayed infrastructure, lower real wages, housing costs, public-investment crowd-out, emigration, delayed family formation, permanent rent dependence, and the future citizen.

In that case, the state does not restructure the bond.

It restructures the citizen.

That is the boundary condition this section is trying to name.

Formal restructuring is only one possible adjustment path. A state with its own currency may also adjust through inflation, exchange-rate depreciation, financial repression, or central-bank support. It may never miss a nominal payment, but citizens may still experience a real loss through lower purchasing power, weaker services, and distorted investment incentives.

A state inside a currency union has fewer release valves.

It cannot simply devalue its own currency.

It does not set monetary policy around domestic conditions alone.

Pressure is more likely to move through wages, employment, fiscal policy, public services, housing, taxation, and emigration.

That is the external constraint from Section 10 returning inside the debt model.

For a eurozone state such as Ireland, the question is not simply whether formal restructuring is desirable. It may not be realistically available except under extreme institutional stress.

The better question is:

If formal restructuring is unavailable,
which balance sheet absorbs the adjustment?

The state balance sheet, the banking system, asset holders, landlords, households, or the future citizen balance sheet?

This is not a moral accusation against creditors.

It is an incidence question.

Where does the burden land?

The model does not treat restructuring as a prediction. It treats restructuring as a boundary condition.

The boundary condition appears when the cost of maintaining continuity begins to damage future citizen solvency faster than it reduces fiscal risk.

A simplified model block looks like this:

restructuring_boundary:
  role: "detect_adjustment_burden_routing_not_recommend_default"

  inputs:
    fiscal_pressure:
      - interest_to_revenue
      - debt_service_burden
      - primary_surplus_required
      - debt_rollover_pressure

    citizen_pressure:
      - public_investment_crowdout
      - public_service_capacity_decline
      - future_tax_burden
      - housing_access_decline
      - ownership_access_decline
      - residual_discretionary_income_decline
      - emigration_pressure
      - future_citizen_solvency_decline

    restructuring_costs:
      - banking_system_exposure
      - market_access_risk
      - legal_institutional_constraint
      - short_term_output_shock
      - reputational_cost
      - contagion_risk

    constraint_flags:
      - currency_regime
      - eurozone_constraint
      - formal_restructuring_feasibility
      - central_bank_backstop_availability

  outputs:
    - continuity_cost_score
    - citizen_solvency_stress_flag
    - informal_adjustment_burden_map
    - policy_restructuring_priority
    - formal_restructuring_unavailable_flag
    - danger_zone_flag

The model does not output:

default now

It outputs:

danger zone

The danger zone is not financial default.

It is citizen-balance-sheet distress before financial default.

The state may still be paying.

The bond market may still be calm.

The dashboard may still say continuity.

But the Future Citizen Balance Sheet may already be deteriorating.

Traditional debt sustainability asks whether the sovereign can continue servicing debt.

Citizen-solvency analysis asks whether continuing to service debt improves or degrades the citizen base that makes repayment meaningful.

The difference matters.

Imagine two paths.

In the first path, the state borrows heavily to build housing, energy infrastructure, transport, schools, water systems, health capacity, and domestic productive industry. Debt rises. Interest costs rise. But future citizens inherit lower housing pressure, better public capital, stronger productivity, better wage capacity, and more independence.

That debt may be legitimate.

It bought capacity.

In the second path, the state borrows or commits future revenue to preserve asset values, subsidize rents, rescue balance sheets, defend bank collateral, maintain consumption, and protect the appearance of growth. Debt rises. Interest costs rise. But future citizens inherit higher taxes, higher rents, higher entry costs, weaker public services, weaker ownership access, and lower independence.

That debt is different.

It bought continuity.

The state may service both debts in the same currency.

The bond market may treat both as obligations.

But the citizen balance sheet does not.

One path gives the future citizen something in return.

The other converts future freedom into present collateral.

That is why the Future Citizen Balance Sheet belongs before any discussion of restructuring.

Without it, default looks like a narrow financial event.

With it, the deeper question becomes visible:

What must future citizens lose so present obligations can be maintained on present terms?

This also changes the meaning of fiscal credibility.

The old question was:

Can the state remain fiscally credible?

The citizen-solvency question is:

Credible to which readout?

Credible to bond markets?

Credible to European fiscal rules?

Credible to asset holders?

Or credible to the future citizens who must live with the result?

A state that protects every balance sheet except the citizen’s has not solved the problem.

It has reassigned it.

The first response should not be formal debt restructuring.

The first response should be policy restructuring.

The first test is whether the state can restructure the policy path before it reaches the debt boundary.

policy restructuring:
    move from dashboard preservation to citizen solvency

capacity restructuring:
    move from demand support to supply expansion

housing-system restructuring:
    move from rent support alone to housing delivery

tax-base restructuring:
    move from concentrated windfalls to durable domestic capacity

investment restructuring:
    move from asset protection to public capital

productive restructuring:
    move from external dependency to domestic productivity

A country should avoid the danger zone long before it reaches it.

It should restructure policy before it restructures debt.

It should move from asset support to capacity building.

It should move from GDP optics to citizen solvency.

It should move from demand subsidies to supply expansion.

It should move from rent support to housing delivery.

It should move from external dependency to domestic productive capacity.

It should move from dashboard preservation to citizen independence.

If it does not, then the default question eventually returns in another form.

Not necessarily as a missed coupon.

Not necessarily as a dramatic announcement.

But as a lived default on the practical conditions of citizenship.

A generation works and cannot own.

A generation pays taxes and receives weaker services.

A generation rents longer and saves less.

A generation delays families.

A generation inherits liabilities without capacity.

The books may balance.

The citizen does not.

That is the model’s most severe warning.

The next section has to ask how it could be wrong.


14. The Missing Objective Function

Before a system reaches the boundary condition from the previous section — where continuity routes its burden through the citizen — the model points to a simpler correction: change the objective function.

The dashboard is not the problem.

The objective function is.

States do not lack dashboards. They have dashboards everywhere: growth, inflation, unemployment, debt, bond spreads, tax receipts, house prices, investment flows, population, exports, borrowing costs, capital inflows. They measure the things that make the state legible to markets, creditors, and itself.

But they do not directly optimize for the human layer.

By human layer, I do not mean only legal citizens. I mean everyone exposed to the conditions the dashboard produces: the people who live inside the system, work inside it, rent or buy shelter inside it, raise children inside it, and inherit its future constraints.

The point is not nationality.

The point is exposure.

A country is not only its balance sheet, its GDP, or its asset base. It is also the people required to live under the conditions those numbers produce.

That is the missing objective function.

Not welfare in the abstract. Not growth in the abstract. Not market confidence in the abstract.

Citizen solvency: the ability of the human layer to live independent lives inside the system the dashboard describes.

A citizen-solvency framework would still measure GDP. It would still care about debt, investment, exports, tax receipts, productivity, and financial stability. Those variables matter. They are not fake. They are not irrelevant. They are just not terminal.

They are inputs.

The model asks a different question: did this policy improve the citizen balance sheet?

That question changes the policy readout.

A policy that raises GDP but worsens housing access is no longer simple success. A policy that increases tax receipts while weakening residual discretionary income is flagged. A policy that protects asset prices while excluding new entrants is not treated as broad prosperity. A policy that increases population without verified housing, infrastructure, public-service, and capital-capacity expansion becomes a solvency risk. A policy that borrows to build productive capacity is distinguished from a policy that borrows to preserve current asset claims.

This is what the Citizen Solvency Model is for. It does not replace politics. It changes what politics can see.

The Revealed Objective Function

No state has to declare that it is optimizing the dashboard. The objective function is revealed by what the system protects when trade-offs become unavoidable.

If housing access weakens but asset prices are protected, the revealed objective is not citizen solvency. If tax receipts rise but future entrants inherit higher entry costs, the revealed objective is not intergenerational capacity. If GDP expands while household independence declines, the revealed objective is not national strength in any human sense. If debt rollover is preserved by transferring adjustment onto renters, workers, children, and future taxpayers, the revealed objective is continuity of the state balance sheet, not solvency of the people living beneath it.

The declared objective may be prosperity. The operational objective may be dashboard continuity.

The current revealed objective often looks like this:

increase GDP
increase tax receipts
preserve asset values
maintain debt rollover
attract capital inflows
maintain bond-market confidence
preserve dashboard continuity

The missing objective is different:

improve current citizen solvency
improve future citizen solvency
increase capacity per citizen
increase residual discretionary income
improve housing access
improve exit power
reduce entry-cost inflation
reduce dependency pressure
reduce fiscal fragility
reduce external fragility
reduce intergenerational burden without capacity

This is not a simple maximize-everything list. Housing access, fiscal sustainability, wage power, public-service capacity, infrastructure delivery, environmental limits, democratic legitimacy, and external constraints cannot all be pushed to their maximum at the same time.

The model is closer to a constrained multi-objective optimizer. It asks what policy does to citizen solvency while exposing the constraints and trade-offs that come with that choice.

citizen_solvency_objective:
  role: "policy_comparison_not_policy_command"

  objective:
    improve:
      - current_citizen_solvency
      - future_citizen_solvency
      - capacity_per_citizen
      - residual_discretionary_income
      - housing_access
      - ownership_access
      - savings_resilience
      - public_service_access
      - exit_power
      - family_formation_security

    reduce:
      - dependency_pressure
      - entry_cost_inflation
      - fiscal_fragility
      - external_fragility
      - intergenerational_burden_without_capacity
      - asset_appreciation_with_entrant_exclusion
      - dashboard_citizen_divergence
      - current_future_divergence

  constraints:
    - fiscal_sustainability
    - external_constraint_layer
    - democratic_legitimacy
    - public_service_capacity
    - environmental_limits
    - administrative_throughput
    - housing_delivery_capacity
    - future_citizen_balance_sheet

  audit_rules:
    - expose_weights
    - expose_trade_offs
    - expose_data_sources
    - expose_distributional_incidence
    - expose_time_lags
    - flag_missing_capacity
    - flag_dashboard_citizen_divergence
    - flag_current_future_divergence
    - flag_objective_function_mismatch

The important line is not only the objective. It is the constraints.

A model that pretends trade-offs do not exist is not a model.

It is propaganda with numbers.

This is also why replacing one dashboard with another is not enough.

The aim is not to install citizen solvency as a new unquestionable metric. The aim is to expose the trade-offs between competing readouts, keep the weights visible, and force the model to show where one definition of success damages another.

The Citizen Solvency Model does not claim that fiscal limits disappear. It does not claim that every subsidy is bad. It does not claim that every debt is destructive. It does not claim that every market signal is wrong.

It asks whether the policy adds capacity or adds pressure.

A state can grow by adding pressure or by adding capacity. Those are different economies. One increases aggregate activity while households become more fragile. The other increases aggregate activity while households become more independent.

The model is not anti-growth. It distinguishes structural capacity deepening from aggregate volume pressure.

Exit Power

Citizen solvency is not only the ability to pay this month’s bills. It is the ability to refuse.

A solvent citizen can leave a bad landlord, reject a bad employer, absorb a temporary shock, move for opportunity, form a household, save for the future, and make choices without immediate collapse.

That capacity is exit power.

When housing costs rise faster than wages, exit power falls. When savings disappear, exit power falls. When ownership becomes unreachable, exit power falls. When public services degrade, exit power falls. When every alternative is more expensive than the trap a person is already in, exit power falls.

This matters because dependency is not only material. It is political.

A population with declining exit power becomes easier to extract from and harder to govern legitimately. That is why the feedback loop from the previous section matters. Citizen pressure is an error signal from the system the dashboard failed to measure. When that signal is ignored, it does not disappear. It returns through politics: voting behaviour, protest, distrust, institutional fatigue, emigration, non-compliance, and loss of legitimacy.

At that point, the objective function updates whether the state wants it to or not.

The dashboard may still say continuity. But the citizen feedback loop has already begun to rewrite the policy environment.

Different Readouts, Different Politics

The same policy can look different through different readouts.

Policy Dashboard view Citizen-solvency view Model output
Demand-side housing support without new supply Helps buyers or renters immediately May capitalize into higher prices or rents Regressive-risk flag
Public housing and infrastructure investment Raises debt in the short term Can improve future citizen capacity Productive-transfer flag
Population growth above housing capacity Raises aggregate GDP May worsen rent, services, savings, and ownership access Capacity-gap flag
Population growth matched by capacity Raises GDP and capacity together Can improve or preserve citizen solvency Scalable-growth flag
Corporation-tax dependence Supports current budget Raises fiscal fragility if concentrated Fiscal-concentration flag
Asset-price support Preserves collateral and wealth effects Can raise future entry costs Entrant-exclusion flag
Domestic productive investment Slower payoff Can improve wages, resilience, and future solvency Capacity-building flag
AI productivity upside Raises productivity and service capacity Can improve wages, throughput, and public-service delivery if gains pass through Solvency-improving growth flag

This is not the model deciding politics. The model does not decide what citizens should value, how much debt is politically acceptable, or which trade-offs a democracy should choose.

Those are political choices.

But the model can make the consequences of those choices visible.

The AI Role

This is where the AI Audit Layer from Section 11 becomes useful. Not because AI has political wisdom. It does not. Not because AI should choose the objective function. It should not.

AI is useful because the policy space is too large to inspect by prose alone.

Each policy has effects across dashboard metrics, current citizen solvency, future citizen solvency, housing capacity, public services, debt service, external fragility, distributional incidence, time lags, scenario assumptions, and political feedback.

A human argument can hide those trade-offs inside language. The audit layer can force them into structure.

For each proposed policy, it can ask:

What happens to GDP?
What happens to debt service?
What happens to housing access?
What happens to rent burden?
What happens to residual discretionary income?
What happens to real wage power?
What happens to public-service capacity?
What happens to exit power?
What happens to future entry costs?
What happens to future citizen solvency?
What happens to political feedback if the citizen readout keeps weakening?
What data source would verify the claim?
What assumption drives the result?
What would falsify it?

The audit layer can then produce a policy comparison output:

Policy A improves GDP but worsens citizen solvency.
Policy B worsens the fiscal deficit temporarily but improves future citizen capacity.
Policy C protects current asset holders but raises future entry costs.
Policy D reduces headline growth but improves household independence.
Policy E raises productivity but only improves citizen solvency if gains pass through to wages, service capacity, housing delivery, or lower non-discretionary costs.
Policy F preserves dashboard continuity but increases dependency pressure and political feedback risk.

That is the useful output. Not a prophecy. Not an oracle. Not an ideological machine.

A contradiction detector.

More formally, it is a dashboard/citizen divergence report.

But the contradiction is not only between GDP and households.

It can also appear between present and future citizens, between fiscal stability and housing access, between asset protection and entry costs, between external confidence and domestic capacity, or between political continuity and exit power.

In plain English, it shows where one readout says success and another readout says failure.

The state optimized the dashboard because the dashboard was visible.

The Citizen Solvency Model changes what is visible.

The broader lesson is not that every system needs this exact model. It is that every optimizer needs a second readout: the thing its main metric fails to see.

For the state, that missing readout is the citizen balance sheet.

For another system, it may be safety, trust, resilience, care quality, institutional legitimacy, or long-term capacity.

That is the family this model belongs to. This post works one case in detail rather than claiming to solve them all. It asks whether a policy path is producing economic independence, or whether households are being pushed into permanent dependence on debt, rent, subsidy, inherited wealth, and systems they cannot enter.

The discovered concept is not simply citizen solvency. It is the revealed objective function: what the state actually preserves when trade-offs become unavoidable.

If the state preserves the dashboard while the citizen layer weakens, the dashboard has become the target. If the state preserves asset values while exit power collapses, asset preservation has outranked citizen independence. If the state preserves debt continuity by transferring adjustment onto future entrants, the future citizen balance sheet has become the hidden liability.

That is not the final answer.

It is the model’s proposed correction.

Make the human layer visible.

Expose the trade-offs.

Track the feedback loop.

Let the readouts contradict each other.

Then ask whether the state is optimizing for dashboard continuity, citizen solvency, or some trade-off it has not admitted.

The next section has to ask how this thesis could be wrong.

If a model cannot be falsified, it is not analysis; it is merely a mood.

This is the falsification pass.

It is the adversarial routine the AI Audit Layer is supposed to generate: the part of the model that searches for null cases, bad edges, bad weights, false correlations, missing variables, and conditions under which its own warning flags should be cleared.

That rule matters.

A useful model must be able to change its mind.

If contradictory evidence appears, the model should not defend its priors. It should downgrade its flags, clear its warnings, revise its weights, or reject its own specification.

Otherwise it is not a model.

It is a bias engine.

The first and most important test is the null case.

The model must be able to find countries, periods, or policies where dashboard growth is real growth.

If GDP rises, GDP per capita rises, housing access improves, real wages rise, savings strengthen, ownership becomes more accessible, public services scale, and future citizen burdens fall, then the dashboard/citizen divergence flag should clear.

In that case, the dashboard would be incomplete, but not misleading.

If the model cannot return that result, it is broken.

If it always finds dependency pressure, it is projecting a conclusion.

The second possibility is that population growth is matched by capacity growth.

If a country increases population while also increasing housing supply, infrastructure, schools, healthcare, transport, energy capacity, public-service capacity, productivity, and capital per worker, then the population-capacity illusion does not apply.

Population growth can be good.

Migration can improve a country.

The model does not deny that.

It only says that population growth without capacity growth can simulate prosperity while worsening citizen solvency.

If capacity scales properly, the model should classify the outcome as scalable growth and clear the capacity-gap flag.

The third possibility is that housing stress is mostly local policy failure and does not need the larger fiscal or global frame.

This is a serious objection.

Planning, zoning, land policy, credit design, tax incentives, public housing delivery, infrastructure sequencing, construction labour, and construction productivity may explain most of the housing problem without invoking global capital, the US financial system, China, eurozone constraints, or sovereign debt.

The model should accept that possibility.

If local housing-supply variables explain most of the citizen-solvency decline, then the external-constraint layer should be downgraded.

The point is not to force every problem into one grand theory.

The point is to measure which mechanisms matter.

The fourth possibility is that the model confuses correlation with causation.

This is one of the hardest tests.

GDP may rise while citizen solvency falls.

Asset prices may rise while ownership access falls.

Debt service may rise while public services weaken.

Those patterns matter.

But correlation is not causation.

The model has to identify mechanisms, not merely co-movement.

If citizen solvency deteriorates for reasons unrelated to dashboard optimization, then the causal edges should be weakened or removed.

If the state is responding to citizen insolvency rather than causing it, then the model must not reverse the direction of the arrow.

A model that cannot distinguish cause from co-movement is only a chart with opinions attached.

The fifth possibility is that AI-driven productivity changes the path.

This is the optimistic falsification case.

If AI produces large, measurable, broad-based productivity gains that raise real wages, lower non-discretionary costs, improve public services, speed up administrative throughput, increase construction productivity, improve housing delivery, and strengthen the domestic tax base, then the citizen-solvency outlook improves.

That would weaken the pessimistic baseline.

The model should allow this.

AI could be a genuine capacity engine.

But it has to show up in the citizen balance sheet.

Not just in valuations, capex, demos, or equity prices.

In wages, housing delivery, public services, productivity, household surplus, and lower dependency pressure.

If AI improves the dashboard while leaving household solvency unchanged, the model should not call that a rescue.

If AI improves the citizen balance sheet, the model should.

The sixth possibility is that debt-funded policy really does buy future capacity.

If borrowing produces enough housing, infrastructure, energy resilience, public capital, domestic productive capacity, and real wage growth, then future citizens may inherit a stronger balance sheet despite inheriting debt.

That is good debt.

The model should classify it as a Productive Transfer.

The thesis is not that debt is evil.

The thesis is that debt must pass the Future Citizen Balance Sheet test.

If debt increases but future entry costs fall, public capital rises, capacity per citizen improves, and ownership access becomes more attainable, then the intergenerational-burden flag should clear.

The seventh possibility is that the Citizen Solvency Dashboard is poorly specified.

The wrong components may be selected. The weights may be wrong. Housing may be double-counted or overweighted. The normalization may be unstable. The data may be weak. National averages may hide regional collapse. Median readings may hide age-cohort exclusion. A composite score may create false precision.

That is why the model should not hide the machinery. It should publish the components, normalization, weights, sensitivity tests, and separate dimensions before aggregating.

A fragile index should not be trusted.

A robust dashboard should survive reasonable changes in assumptions.

If the result disappears when weights are changed within a reasonable range, then the model is not measuring the world.

It is measuring its own assumptions.

The eighth possibility is that the model is too dependent on Canada and Ireland.

Those cases are useful.

But they may not generalize.

Canada may be a capacity-gap case.

Ireland may be a small-state measurement and eurozone-constraint case.

That does not prove the model travels.

The model should be tested on countries and periods that are not selected because they fit the thesis.

It should find some dashboard/citizen divergence cases.

It should also find real-growth cases.

If it cannot identify both, it is not general enough.

The ninth possibility is that the model hides political value judgements inside technical weights.

Citizen solvency is a value-laden target.

That does not make it wrong.

But it means the model must be honest about its normative assumptions.

How much weight should housing access receive?

How much weight should ownership receive compared with secure rental access?

How much weight should future citizens receive compared with current citizens?

How much fiscal risk is acceptable for capacity building?

Those are not purely technical choices.

They are political choices made visible through model architecture.

If the weights can be tuned to prove any conclusion, the model fails.

If the model hides those weights, it fails faster.

The tenth possibility is that restructuring is far more damaging than continued servicing.

This must be taken seriously.

Restructuring can cause banking stress, legal conflict, market exclusion, capital flight, contagion, recession, institutional conflict, and long-term reputational cost.

The model should not romanticize it.

It should treat restructuring as a boundary condition, not a policy desire.

The danger-zone flag could be a false positive.

If continued servicing produces less harm to future citizen solvency than formal restructuring, informal adjustment, market exclusion, or institutional conflict, then the danger-zone flag should be suppressed.

For eurozone states, formal restructuring may also be institutionally unavailable or so destructive that the relevant test is not debt restructuring at all.

The relevant test may be policy restructuring.

The purpose is not to advocate default.

The purpose is to detect when the repayment path itself becomes destructive.

In implementation terms, the falsification registry would look something like this:

falsification_registry:
  role: "make_model_update_or_fail"

  global_rule:
    - contradictory_evidence_must_clear_or_downgrade_flags
    - missing_data_must_return_inconclusive_not_confident
    - unstable_weights_must_trigger_specification_warning
    - causal_edges_must_be_supported_by_mechanism_not_correlation_only

  tests:
    - id: dashboard_citizen_null_case
      condition: "dashboard improves and citizen_solvency improves"
      response: "clear dashboard_citizen_divergence_flag; classify as real_growth"

    - id: capacity_matched_population_growth
      condition: "population growth is matched by housing, infrastructure, public services, productivity, and capital per worker"
      response: "clear capacity_gap_flag; classify as scalable_growth"

    - id: local_housing_policy_dominance
      condition: "local planning, zoning, land, delivery, and construction variables explain most housing stress"
      response: "downgrade external_constraint_weight"

    - id: causal_identification_failure
      condition: "co-movement exists but mechanism is unsupported or arrow direction is wrong"
      response: "weaken_or_remove_structural_edge"

    - id: ai_productivity_upside
      condition: "AI raises real wages, household surplus, service delivery, administrative throughput, and housing delivery"
      response: "classify AI as productive_capacity_expansion"

    - id: productive_debt_transfer
      condition: "debt increases but public capital rises and future entry costs fall"
      response: "classify as productive_transfer; clear intergenerational_burden_flag"

    - id: index_specification_fragility
      condition: "reasonable changes in weights, normalization, or components reverse the result"
      response: "mark Citizen Solvency Dashboard as fragile; publish dimension-level results only"

    - id: generalization_failure
      condition: "model only works on selected Canada/Ireland cases and fails out-of-sample"
      response: "downgrade general thesis; treat as case-specific"

    - id: weighting_bias
      condition: "weights can be tuned to generate any desired conclusion"
      response: "expose normative weights; reject single aggregate score"

    - id: restructuring_danger_zone_false_positive
      condition: "continued servicing improves future citizen solvency more than alternatives"
      response: "suppress danger_zone_flag"

The falsification table should therefore include the model response, not just the objection.

Claim What would weaken it Model response
Dashboard growth can diverge from citizen solvency GDP rises and citizen-solvency indicators improve together Clear dashboard/citizen divergence flag; classify as real growth
Population growth can simulate prosperity Capacity scales with population and citizen solvency improves Clear capacity-gap flag; classify as scalable growth
External constraints materially explain citizen-solvency decline Local housing policy and construction capacity explain most of the decline Downgrade external-constraint weight
Housing is the main transmission mechanism Housing access improves while citizen solvency still falls for unrelated reasons Downgrade housing weight; search for other mechanisms
Dashboard/citizen divergence is causal Co-movement exists, but causal mechanism is weak or reversed Remove or weaken causal edge
Debt can become intergenerationally regressive Debt-funded policy improves future public capital and reduces entry costs Classify as Productive Transfer; clear intergenerational-burden flag
Asset support harms future entrants Asset support does not raise future entry costs or reduce ownership access Clear entrant-exclusion flag
AI productivity does not rescue the baseline AI produces broad-based wage, productivity, service, housing-delivery, and household-surplus gains Classify AI as productive-capacity expansion
Citizen Solvency Dashboard is robust Results disappear under reasonable alternative weights, components, or normalizations Mark dashboard fragile; revise specification
Model generalizes beyond selected cases Out-of-sample countries do not show the predicted relationships Downgrade general thesis; treat as country-specific
Model is not hiding value judgements Weight choices determine the result Expose weights; reject single-score conclusion
Restructuring danger-zone flag is valid Continued servicing improves future citizen solvency more than restructuring or forced adjustment Suppress danger-zone flag

This is the standard the post should hold itself to.

The claim is not that the model is right because it feels right. The claim is that the structure, assumptions, data, and tests are fully exposed, including exactly how the thesis could fail, and how the model would update if those tests succeed.

That is what makes the argument worth making.

If the model survives these tests, the conclusion is not that the dashboard is useless.

It is that the dashboard is not the final readout.

The final readout is the citizen balance sheet.


16. The Citizen Is the Final Readout

Housing, debt, and productivity all matter, but the deeper failure is measurement. A system optimizes what it measures; when the measurement is wrong, the objective function eventually becomes wrong too.

And in systems that optimize what they measure, a measurement crisis becomes an objective-function crisis.

The state optimized the dashboard and lost the citizen.

It protected the numbers that made the system look continuous: GDP, tax receipts, asset prices, debt rollover, capital inflows, population growth, and investor confidence.

Those numbers matter, but they are not the country; they are merely readouts.

The country is the citizen who must live inside the system those readouts describe.

A citizen who cannot afford shelter, build savings, form a family securely, refuse a bad landlord or employer, or inherit capacity alongside debt is not living inside a solvent country, whatever the dashboard says.

A country can appear rich while hollowing out the people who make it real.

That is the error.

The national balance sheet is not complete unless it includes the citizen balance sheet.

The sovereign debt model is not complete unless it includes the future citizen who must service the debt.

The housing model is not complete unless it asks who can enter secure shelter and ownership, not only who owns today.

The migration model is not complete unless it asks whether capacity scales with population.

The growth model is not complete unless it asks whether growth adds capacity or merely adds pressure.

This is what the Citizen Solvency Model tries to formalize.

The model does not predict the future, replace economics, or let AI solve politics. It makes a narrower claim: the citizen balance sheet should be visible beside the state dashboard.

It changes the question.

Instead of asking only:

Is the economy growing?

the model also asks:

Are citizens becoming more capable of living freely inside it?

Instead of asking only:

Can the state service the debt?

the model also asks:

What must future citizens lose for the state to keep servicing it?

Instead of asking only:

Are asset values rising?

the model also asks:

Who is being priced out by those asset values?

Instead of asking only:

Did the policy improve the dashboard?

the model also asks:

Did the policy improve the citizen balance sheet?

That is the contribution.

The dashboard is not useless.

GDP still matters.

Debt still matters.

Investment, productivity, employment, and financial stability still matter.

But they are not the final readout.

A better model would not reject growth.

It would distinguish growth that adds capacity from growth that adds pressure.

A state cannot be solvent if its citizens are becoming insolvent.

A country cannot call itself rich if the next generation must borrow more, rent longer, save less, own less, and have less room to say no merely to enter the life the dashboard says has been preserved.

This is not prosperity.

It is continuity purchased with dependency.

The dashboard should remain.

But the citizen must become the final readout.


Appendix A: Citizen Solvency Model v0.1

The main post argues that the national dashboard and the citizen balance sheet can diverge.

This appendix provides the first executable prototype of that claim: a minimal Python implementation that ingests country data, applies simple thresholds, emits diagnostic flags, generates chart outputs, and produces post-ready tables.

This version does two things.

First, it preserves the model skeleton. It shows how the Citizen Solvency Model can be expressed as code rather than prose alone.

Second, it adds a first empirical run for Canada and Ireland using public-data readouts.

That matters because the post should not merely describe what the model would look for. It should show the model touching reality, even in a limited v0.1 form.

The prototype remains deliberately modest. It is not a finished econometric system. It does not prove causation. It does not replace a full empirical model. But it does show that the argument can be translated into inspectable machinery with explicit inputs, thresholds, outputs, and flags.

The script demonstrates two initial country instances.

The first is Canada.

For Canada, the model now includes the section’s core test: the population-capacity gap. It compares household formation with housing-capacity expansion.

The Canada first-run test asks:

Did household demand outrun housing-capacity expansion?

The Canada instance can emit flags such as:

canada_capacity_gap_flag
canada_required_build_rate_gap_flag
canada_dashboard_citizen_divergence_provisional

The second instance is Ireland.

For Ireland, the model separates headline GDP from domestic-capacity readouts such as GNP and Modified Domestic Demand. It also includes a corporation-tax concentration flag.

The Ireland first-run test asks:

Did headline GDP diverge from domestic-capacity measures?
Did fiscal strength depend on a highly concentrated revenue base?

The Ireland instance can emit flags such as:

ireland_gdp_mdd_divergence_flag
ireland_gdp_gnp_divergence_flag
ireland_multinational_domestic_sector_divergence_flag
ireland_fiscal_concentration_flag
ireland_dashboard_citizen_divergence_flag

The Ireland version preserves an important guardrail: GDP/GNI* or GDP/MDD divergence does not prove citizen poverty. It proves measurement distortion. Citizen pressure has to be measured separately, using household-side indicators such as at-risk-of-poverty, enforced deprivation, rent burden, residual income, savings resilience, or wage data.

The outputs are deliberately plain:

Canada capacity-gap CSV
Ireland measurement-divergence CSV
JSON country reports
flag table
post-ready markdown tables
Canada capacity-gap chart
Ireland dashboard/domestic-readout chart
Ireland fiscal-concentration chart

The script also keeps optional support for deeper later versions: Canada GDP-per-capita versus wage-power analysis, wage microdata trimming, and Ireland time-series analysis.

Where wage microdata are available, the model can remove the top 5% and bottom 5% of wage observations by year before computing the wage readout. This reduces distortion from extreme earners at either end of the distribution. Where only aggregate wage data are available, the script falls back to the supplied wage or median-income series and records that limitation.

This appendix therefore does not claim to finish the model.

It proves three narrower things:

1. the model has an executable shape;
2. the Canada and Ireland examples can be run through that shape;
3. the first empirical readout produces visible flags rather than prose-only claims.

A later version should publish the full source registry, expose thresholds in YAML, add country configuration files, run sensitivity tests, expand citizen-pressure indicators, and include a real null-case country where the model correctly clears its own warning flags.

Show Python prototype: Citizen Solvency Model v0.1
#!/usr/bin/env python3
"""
appendix_citizen_solvency_model_v01.py

Technical Appendix A — Citizen Solvency Model v0.1

Purpose:
    Demonstrate the executable shape of the Citizen Solvency Model.

This script is not a finished econometric model.
It is a first runnable prototype.

It does two jobs:

    1. First empirical run:
        Canada:
            household formation versus housing-capacity expansion.

        Ireland:
            headline GDP versus domestic-capacity readouts,
            plus corporation-tax concentration.

    2. Optional deeper inputs:
        Canada:
            GDP per capita versus CPI-deflated wage power,
            with optional wage microdata trimming.

        Ireland:
            GDP / GNI* / Modified Domestic Demand time series,
            with optional citizen-pressure indicators.

Important guardrails:
    - GDP/GNI* divergence does not prove poverty.
    - GDP/MDD divergence does not prove poverty.
    - These divergences prove measurement distortion.
    - Citizen pressure must be measured separately using household-side indicators.
    - Canada capacity pressure does not prove causation.
    - It tests whether demand pressure outran capacity expansion.

Default run:
    python appendix_citizen_solvency_model_v01.py --out reports/csm_v01

Synthetic demonstration mode:
    python appendix_citizen_solvency_model_v01.py --synthetic --out reports/csm_v01_synthetic

Optional CSV inputs:
    python appendix_citizen_solvency_model_v01.py \
        --canada-capacity data/canada_capacity.csv \
        --ireland-first-run data/ireland_first_run.csv \
        --out reports/csm_v01

Expected Canada capacity CSV columns:
    period
    household_formation
    net_housing_completions
    baseline_projected_net_completions_annual
    required_net_completions_to_close_gap_annual

Expected Ireland first-run CSV columns:
    period
    gdp_growth
    gnp_growth
    modified_domestic_demand_growth
    domestic_sector_growth
    multinational_sector_growth
    corporation_tax_top_three_share
    corporation_tax_top_three_eur_billion

Optional Canada wage CSV columns:
    year
    gdp_per_capita
    nominal_wage
    cpi

Optional Canada wage CSV columns:
    median_wage
    rent_index
    house_price_index
    ownership_access_index

Optional wage microdata CSV:
    year
    wage

Optional Ireland time-series CSV columns:
    year
    gdp
    gni_star
    modified_domestic_demand

Optional Ireland citizen-pressure columns:
    at_risk_poverty_rate
    enforced_deprivation_rate
    median_equivalised_income
    rent_burden
    house_price_to_income
"""

from __future__ import annotations

import argparse
import json
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any

import matplotlib.pyplot as plt
import pandas as pd

# ---------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------

@dataclass
class ModelThresholds:
    """
    First-pass thresholds.

    These are deliberately simple.
    A later version should expose them in YAML and run sensitivity tests.
    """

    # Canada capacity-gap thresholds
    canada_capacity_gap_ratio_warning: float = 1.10
    canada_capacity_gap_units_warning: float = 100_000.0
    canada_required_build_rate_gap_warning: float = 25_000.0

    # Canada wage/dashboard thresholds
    canada_dashboard_wage_gap_points: float = 15.0
    canada_real_wage_decline_points: float = -5.0

    # Ireland dashboard/domestic-readout thresholds
    ireland_gdp_mdd_growth_gap_points: float = 5.0
    ireland_gdp_gnp_growth_gap_points: float = 5.0
    ireland_multinational_domestic_sector_gap_points: float = 10.0

    # Ireland fiscal-concentration threshold
    ireland_corporation_tax_top_three_share_warning: float = 25.0

    # Ireland citizen-pressure thresholds
    ireland_poverty_rate_warning: float = 12.0
    ireland_deprivation_warning: float = 15.0

    # Optional wage microdata trimming
    trim_lower_quantile: float = 0.05
    trim_upper_quantile: float = 0.95

@dataclass
class Flag:
    id: str
    severity: str
    fired: bool
    evidence: str

@dataclass
class CountryReport:
    country: str
    model_instance: str
    readouts: dict[str, Any]
    flags: list[Flag]
    notes: list[str]
    source_registry: list[dict[str, str]]

# ---------------------------------------------------------------------
# Source registry
# ---------------------------------------------------------------------

SOURCE_REGISTRY = {
    "canada_pbo_2025_housing_gap": {
        "source": "Parliamentary Budget Officer, Canada",
        "publication": "Household Formation and the Housing Stock: Estimating the Housing Gap in 2035",
        "date": "2025-08-26",
        "used_for": (
            "Canada household formation, net housing completions, "
            "baseline projected completions, and required completions to close the gap."
        ),
    },
    "ireland_cso_qna_q1_2026": {
        "source": "Central Statistics Office, Ireland",
        "publication": "Quarterly National Accounts Quarter 1 2026 Provisional",
        "date": "2026-06-04",
        "used_for": (
            "Ireland Q1 2026 GDP, GNP, Modified Domestic Demand, "
            "domestic sector, and multinational-dominated sector growth."
        ),
    },
    "ireland_ifac_corporation_tax_2026": {
        "source": "Irish Fiscal Advisory Council",
        "publication": (
            "More concentration, more risk: three firms account for almost half "
            "of Ireland's corporation tax revenues"
        ),
        "date": "2026-02-19",
        "used_for": (
            "Ireland top-three corporation-tax concentration share and estimated "
            "euro amount for 2024."
        ),
    },
}

# ---------------------------------------------------------------------
# Utility functions
# ---------------------------------------------------------------------

def ensure_out_dir(out_dir: Path) -> Path:
    out_dir.mkdir(parents=True, exist_ok=True)
    return out_dir

def require_columns(df: pd.DataFrame, cols: list[str], name: str) -> None:
    missing = [c for c in cols if c not in df.columns]
    if missing:
        raise ValueError(f"{name} is missing required columns: {missing}")

def numeric(df: pd.DataFrame, cols: list[str]) -> pd.DataFrame:
    out = df.copy()
    for col in cols:
        if col in out.columns:
            out[col] = pd.to_numeric(out[col], errors="coerce")
    return out

def latest_valid(df: pd.DataFrame, col: str) -> tuple[Any, float]:
    valid = df[[df.columns[0], col]].dropna()
    if valid.empty:
        raise ValueError(f"No valid values for {col}")
    row = valid.iloc[-1]
    return row[df.columns[0]], float(row[col])

def first_valid(df: pd.DataFrame, col: str) -> tuple[Any, float]:
    valid = df[[df.columns[0], col]].dropna()
    if valid.empty:
        raise ValueError(f"No valid values for {col}")
    row = valid.iloc[0]
    return row[df.columns[0]], float(row[col])

def index_to_base_year(
    df: pd.DataFrame,
    value_col: str,
    year_col: str = "year",
    base_year: int | None = None,
    output_col: str | None = None,
) -> pd.DataFrame:
    """
    Convert a series to an index where base_year = 100.

    If base_year is None, the earliest available year is used.
    """
    out = df.copy()
    out = out.sort_values(year_col)

    if base_year is None:
        base_year = int(out[year_col].min())

    base_rows = out.loc[out[year_col] == base_year, value_col].dropna()
    if base_rows.empty:
        raise ValueError(f"No value for {value_col} in base year {base_year}")

    base_value = float(base_rows.iloc[0])
    if base_value == 0:
        raise ValueError(f"Base value for {value_col} is zero")

    if output_col is None:
        output_col = f"{value_col}_index"

    out[output_col] = (out[value_col] / base_value) * 100.0
    return out

def index_point_change(first: float, last: float) -> float:
    return float(last - first)

def write_json(payload: Any, path: Path) -> None:
    path.write_text(json.dumps(payload, indent=2), encoding="utf-8")

def write_report(report: CountryReport, path: Path) -> None:
    write_json(asdict(report), path)

def flags_to_frame(report: CountryReport) -> pd.DataFrame:
    return pd.DataFrame([asdict(f) for f in report.flags])

def source_entries(*keys: str) -> list[dict[str, str]]:
    return [SOURCE_REGISTRY[k] for k in keys]

# ---------------------------------------------------------------------
# Canada first empirical run: capacity gap
# ---------------------------------------------------------------------

def first_run_canada_capacity_data() -> pd.DataFrame:
    """
    First empirical Canada capacity-gap input.

    Source:
        Parliamentary Budget Officer, Canada.
        Household Formation and the Housing Stock: Estimating the Housing Gap in 2035.

    Values used:
        2024 net household formation: 482,000
        2024 net housing completions: 276,000
        Baseline projected net completions, annual average 2025-2035: 227,000
        Required completions to close gap, annual average 2025-2035: 290,000
    """
    return pd.DataFrame(
        {
            "period": ["2024"],
            "household_formation": [482_000],
            "net_housing_completions": [276_000],
            "baseline_projected_net_completions_annual": [227_000],
            "required_net_completions_to_close_gap_annual": [290_000],
            "source_key": ["canada_pbo_2025_housing_gap"],
        }
    )

def synthetic_canada_capacity_data() -> pd.DataFrame:
    """
    Synthetic Canada-like capacity data.

    This is not publication data.
    It exists only to show how the branch behaves when run on constructed data.
    """
    return pd.DataFrame(
        {
            "period": ["synthetic_demo"],
            "household_formation": [500_000],
            "net_housing_completions": [250_000],
            "baseline_projected_net_completions_annual": [220_000],
            "required_net_completions_to_close_gap_annual": [310_000],
            "source_key": ["synthetic_demo_not_publication_data"],
        }
    )

def run_canada_capacity_model(
    canada: pd.DataFrame,
    thresholds: ModelThresholds,
) -> tuple[pd.DataFrame, CountryReport]:
    require_columns(
        canada,
        [
            "period",
            "household_formation",
            "net_housing_completions",
            "baseline_projected_net_completions_annual",
            "required_net_completions_to_close_gap_annual",
        ],
        "canada capacity data",
    )

    df = numeric(
        canada,
        [
            "household_formation",
            "net_housing_completions",
            "baseline_projected_net_completions_annual",
            "required_net_completions_to_close_gap_annual",
        ],
    )

    df["capacity_gap_units"] = df["household_formation"] - df["net_housing_completions"]
    df["demand_to_completion_ratio"] = df["household_formation"] / df["net_housing_completions"]

    df["required_build_rate_gap_units"] = (
        df["required_net_completions_to_close_gap_annual"]
        - df["baseline_projected_net_completions_annual"]
    )

    df["required_to_baseline_build_rate_ratio"] = (
        df["required_net_completions_to_close_gap_annual"]
        / df["baseline_projected_net_completions_annual"]
    )

    row = df.iloc[-1]
    period = str(row["period"])

    gap_units = float(row["capacity_gap_units"])
    demand_ratio = float(row["demand_to_completion_ratio"])
    required_gap = float(row["required_build_rate_gap_units"])
    required_ratio = float(row["required_to_baseline_build_rate_ratio"])

    capacity_gap_fired = (
        demand_ratio >= thresholds.canada_capacity_gap_ratio_warning
        and gap_units >= thresholds.canada_capacity_gap_units_warning
    )

    required_build_rate_gap_fired = (
        required_gap >= thresholds.canada_required_build_rate_gap_warning
    )

    flags = [
        Flag(
            id="canada_capacity_gap_flag",
            severity="high" if capacity_gap_fired else "low",
            fired=bool(capacity_gap_fired),
            evidence=(
                f"In {period}, household formation was {row['household_formation']:,.0f} "
                f"while net housing completions were {row['net_housing_completions']:,.0f}. "
                f"Gap = {gap_units:,.0f}; demand/completion ratio = {demand_ratio:.2f}."
            ),
        ),
        Flag(
            id="canada_required_build_rate_gap_flag",
            severity="medium" if required_build_rate_gap_fired else "low",
            fired=bool(required_build_rate_gap_fired),
            evidence=(
                f"Projected baseline net completions average "
                f"{row['baseline_projected_net_completions_annual']:,.0f} annually, "
                f"while the required annual rate to close the gap is "
                f"{row['required_net_completions_to_close_gap_annual']:,.0f}. "
                f"Required gap = {required_gap:,.0f}; required/baseline ratio = {required_ratio:.2f}."
            ),
        ),
        Flag(
            id="canada_dashboard_citizen_divergence_provisional",
            severity="medium" if capacity_gap_fired else "low",
            fired=bool(capacity_gap_fired),
            evidence=(
                "Provisional flag: Canada shows a housing-capacity gap in the first empirical run. "
                "A full dashboard/citizen divergence result requires adding wage, rent burden, "
                "ownership access, and household-resilience indicators."
            ),
        ),
    ]

    notes = [
        "Canada v0.1 applies the population-capacity mechanism to public housing data.",
        "This first run tests whether household formation outran net housing completions.",
        "The result is not a causal proof.",
        "It is a first empirical readout showing whether the Canada capacity-gap claim appears in public data.",
        "The dashboard/citizen divergence flag remains provisional until wage, rent, ownership, and household-resilience series are added.",
    ]

    report = CountryReport(
        country="Canada",
        model_instance="capacity_gap_first_empirical_run",
        readouts={
            "period": period,
            "household_formation": int(row["household_formation"]),
            "net_housing_completions": int(row["net_housing_completions"]),
            "capacity_gap_units": int(gap_units),
            "demand_to_completion_ratio": round(demand_ratio, 2),
            "baseline_projected_net_completions_annual": int(
                row["baseline_projected_net_completions_annual"]
            ),
            "required_net_completions_to_close_gap_annual": int(
                row["required_net_completions_to_close_gap_annual"]
            ),
            "required_build_rate_gap_units": int(required_gap),
            "required_to_baseline_build_rate_ratio": round(required_ratio, 2),
        },
        flags=flags,
        notes=notes,
        source_registry=source_entries("canada_pbo_2025_housing_gap")
        if "synthetic" not in str(row.get("source_key", ""))
        else [],
    )

    return df, report

# ---------------------------------------------------------------------
# Optional Canada wage/dashboard model
# ---------------------------------------------------------------------

def trim_wage_microdata(
    micro: pd.DataFrame,
    thresholds: ModelThresholds,
    year_col: str = "year",
    wage_col: str = "wage",
) -> pd.DataFrame:
    """
    Trim top/bottom wage observations by year.

    This requires wage-level observations.
    It cannot be done from aggregate wage data.

    Note:
        With tiny demo samples, a 5% trim may remove little or nothing.
        The feature is intended for real wage microdata.
    """
    require_columns(micro, [year_col, wage_col], "wage microdata")
    micro = numeric(micro, [year_col, wage_col]).dropna()

    rows = []
    for year, group in micro.groupby(year_col):
        lo = group[wage_col].quantile(thresholds.trim_lower_quantile)
        hi = group[wage_col].quantile(thresholds.trim_upper_quantile)
        trimmed = group[(group[wage_col] >= lo) & (group[wage_col] <= hi)]

        rows.append(
            {
                "year": int(year),
                "trimmed_mean_wage": float(trimmed[wage_col].mean()),
                "trimmed_median_wage": float(trimmed[wage_col].median()),
                "observations": int(len(group)),
                "kept_observations": int(len(trimmed)),
                "lower_cutoff": float(lo),
                "upper_cutoff": float(hi),
            }
        )

    return pd.DataFrame(rows).sort_values("year")

def prepare_canada_wage_data(
    canada: pd.DataFrame,
    thresholds: ModelThresholds,
    wage_microdata: pd.DataFrame | None = None,
) -> pd.DataFrame:
    require_columns(canada, ["year", "gdp_per_capita", "nominal_wage", "cpi"], "canada wage data")

    canada = numeric(
        canada,
        [
            "year",
            "gdp_per_capita",
            "nominal_wage",
            "median_wage",
            "cpi",
            "rent_index",
            "house_price_index",
            "ownership_access_index",
        ],
    ).sort_values("year")

    if wage_microdata is not None:
        trimmed = trim_wage_microdata(wage_microdata, thresholds)
        canada = canada.merge(trimmed[["year", "trimmed_median_wage"]], on="year", how="left")
        canada["wage_readout"] = canada["trimmed_median_wage"].combine_first(canada["nominal_wage"])
        canada["wage_readout_source"] = "trimmed_median_wage_if_available_else_nominal_wage"
    elif "median_wage" in canada.columns and canada["median_wage"].notna().any():
        canada["wage_readout"] = canada["median_wage"]
        canada["wage_readout_source"] = "median_wage"
    else:
        canada["wage_readout"] = canada["nominal_wage"]
        canada["wage_readout_source"] = "nominal_wage"

    canada["real_wage_proxy"] = canada["wage_readout"] / canada["cpi"] * 100.0

    base_year = int(canada["year"].min())
    canada = index_to_base_year(canada, "gdp_per_capita", base_year=base_year, output_col="gdp_pc_index")
    canada = index_to_base_year(canada, "real_wage_proxy", base_year=base_year, output_col="real_wage_index")

    canada["dashboard_minus_wage_gap"] = canada["gdp_pc_index"] - canada["real_wage_index"]

    return canada

def run_canada_wage_model(
    canada: pd.DataFrame,
    thresholds: ModelThresholds,
    wage_microdata: pd.DataFrame | None = None,
) -> tuple[pd.DataFrame, CountryReport]:
    df = prepare_canada_wage_data(canada, thresholds, wage_microdata=wage_microdata)

    start_year, start_gap = first_valid(df, "dashboard_minus_wage_gap")
    end_year, end_gap = latest_valid(df, "dashboard_minus_wage_gap")
    _, start_real_wage = first_valid(df, "real_wage_index")
    _, end_real_wage = latest_valid(df, "real_wage_index")
    _, start_gdp = first_valid(df, "gdp_pc_index")
    _, end_gdp = latest_valid(df, "gdp_pc_index")

    wage_change = index_point_change(start_real_wage, end_real_wage)
    gdp_change = index_point_change(start_gdp, end_gdp)
    gap_change = index_point_change(start_gap, end_gap)

    flags = [
        Flag(
            id="canada_dashboard_wage_gap_flag",
            severity="high" if end_gap >= thresholds.canada_dashboard_wage_gap_points else "low",
            fired=bool(end_gap >= thresholds.canada_dashboard_wage_gap_points),
            evidence=(
                f"By {end_year}, GDP-per-capita index minus real-wage index = "
                f"{end_gap:.1f} points. Threshold = "
                f"{thresholds.canada_dashboard_wage_gap_points:.1f}."
            ),
        ),
        Flag(
            id="canada_relative_wage_pressure_flag",
            severity="medium" if gap_change > 0 else "low",
            fired=bool(gap_change > 0),
            evidence=(
                f"Dashboard/wage gap changed from {start_gap:.1f} in {start_year} "
                f"to {end_gap:.1f} in {end_year}."
            ),
        ),
        Flag(
            id="canada_real_wage_decline_flag",
            severity="medium" if wage_change <= thresholds.canada_real_wage_decline_points else "low",
            fired=bool(wage_change <= thresholds.canada_real_wage_decline_points),
            evidence=(
                f"Real-wage index changed by {wage_change:.1f} points "
                f"from {start_year} to {end_year}."
            ),
        ),
    ]

    notes = [
        "Optional Canada wage/dashboard model compares GDP per capita with CPI-deflated wage power.",
        "This should not be used as the main Canada finding unless populated with official wage and CPI series.",
        "If wage microdata is supplied, top/bottom 5% of wage observations are trimmed by year.",
        "If only aggregate wage data is supplied, true trimming is not possible.",
    ]

    report = CountryReport(
        country="Canada",
        model_instance="optional_dashboard_wage_power",
        readouts={
            "start_year": int(start_year),
            "end_year": int(end_year),
            "gdp_pc_index_start": round(start_gdp, 2),
            "gdp_pc_index_end": round(end_gdp, 2),
            "real_wage_index_start": round(start_real_wage, 2),
            "real_wage_index_end": round(end_real_wage, 2),
            "gdp_pc_index_change": round(gdp_change, 2),
            "real_wage_index_change": round(wage_change, 2),
            "dashboard_minus_wage_gap_start": round(start_gap, 2),
            "dashboard_minus_wage_gap_end": round(end_gap, 2),
            "dashboard_minus_wage_gap_change": round(gap_change, 2),
        },
        flags=flags,
        notes=notes,
        source_registry=[],
    )

    return df, report

# ---------------------------------------------------------------------
# Ireland first empirical run: dashboard/domestic/fiscal divergence
# ---------------------------------------------------------------------

def first_run_ireland_data() -> pd.DataFrame:
    """
    First empirical Ireland input.

    Sources:
        Central Statistics Office, Ireland, Q1 2026 Quarterly National Accounts.
        Irish Fiscal Advisory Council, corporation-tax concentration analysis.

    Values used:
        GDP growth, Q1 2026: -12.1
        GNP growth, Q1 2026: +1.5
        Modified Domestic Demand growth, Q1 2026: +0.6
        Domestic sectors growth, Q1 2026: +0.4
        Multinational-dominated sectors growth, Q1 2026: -27.1
        Top three corporation-tax share, 2024: 46%
        Top three corporation-tax estimate, 2024: roughly €13 billion
    """
    return pd.DataFrame(
        {
            "period": ["Q1 2026"],
            "gdp_growth": [-12.1],
            "gnp_growth": [1.5],
            "modified_domestic_demand_growth": [0.6],
            "domestic_sector_growth": [0.4],
            "multinational_sector_growth": [-27.1],
            "corporation_tax_top_three_share": [46.0],
            "corporation_tax_top_three_eur_billion": [13.0],
            "source_key": ["ireland_cso_qna_q1_2026;ireland_ifac_corporation_tax_2026"],
        }
    )

def synthetic_ireland_data() -> pd.DataFrame:
    """
    Synthetic Ireland-like data.

    This is not publication data.
    It exists only to show how the branch behaves when run on constructed data.
    """
    return pd.DataFrame(
        {
            "period": ["synthetic_demo"],
            "gdp_growth": [-10.0],
            "gnp_growth": [2.0],
            "modified_domestic_demand_growth": [1.0],
            "domestic_sector_growth": [0.5],
            "multinational_sector_growth": [-25.0],
            "corporation_tax_top_three_share": [45.0],
            "corporation_tax_top_three_eur_billion": [12.0],
            "source_key": ["synthetic_demo_not_publication_data"],
        }
    )

def run_ireland_first_run_model(
    ireland: pd.DataFrame,
    thresholds: ModelThresholds,
) -> tuple[pd.DataFrame, CountryReport]:
    require_columns(
        ireland,
        [
            "period",
            "gdp_growth",
            "gnp_growth",
            "modified_domestic_demand_growth",
            "domestic_sector_growth",
            "multinational_sector_growth",
            "corporation_tax_top_three_share",
            "corporation_tax_top_three_eur_billion",
        ],
        "ireland first-run data",
    )

    df = numeric(
        ireland,
        [
            "gdp_growth",
            "gnp_growth",
            "modified_domestic_demand_growth",
            "domestic_sector_growth",
            "multinational_sector_growth",
            "corporation_tax_top_three_share",
            "corporation_tax_top_three_eur_billion",
        ],
    )

    df["gdp_mdd_gap_points"] = (
        df["gdp_growth"] - df["modified_domestic_demand_growth"]
    ).abs()

    df["gdp_gnp_gap_points"] = (
        df["gdp_growth"] - df["gnp_growth"]
    ).abs()

    df["multinational_domestic_sector_gap_points"] = (
        df["multinational_sector_growth"] - df["domestic_sector_growth"]
    ).abs()

    row = df.iloc[-1]
    period = str(row["period"])

    gdp_mdd_gap = float(row["gdp_mdd_gap_points"])
    gdp_gnp_gap = float(row["gdp_gnp_gap_points"])
    mne_domestic_gap = float(row["multinational_domestic_sector_gap_points"])
    corp_tax_share = float(row["corporation_tax_top_three_share"])

    gdp_mdd_fired = gdp_mdd_gap >= thresholds.ireland_gdp_mdd_growth_gap_points
    gdp_gnp_fired = gdp_gnp_gap >= thresholds.ireland_gdp_gnp_growth_gap_points
    mne_domestic_fired = (
        mne_domestic_gap >= thresholds.ireland_multinational_domestic_sector_gap_points
    )
    fiscal_concentration_fired = (
        corp_tax_share >= thresholds.ireland_corporation_tax_top_three_share_warning
    )

    flags = [
        Flag(
            id="ireland_gdp_mdd_divergence_flag",
            severity="high" if gdp_mdd_fired else "low",
            fired=bool(gdp_mdd_fired),
            evidence=(
                f"In {period}, GDP growth was {row['gdp_growth']:.1f}% while "
                f"Modified Domestic Demand growth was "
                f"{row['modified_domestic_demand_growth']:.1f}%. "
                f"Absolute gap = {gdp_mdd_gap:.1f} percentage points."
            ),
        ),
        Flag(
            id="ireland_gdp_gnp_divergence_flag",
            severity="high" if gdp_gnp_fired else "low",
            fired=bool(gdp_gnp_fired),
            evidence=(
                f"In {period}, GDP growth was {row['gdp_growth']:.1f}% while "
                f"GNP growth was {row['gnp_growth']:.1f}%. "
                f"Absolute gap = {gdp_gnp_gap:.1f} percentage points."
            ),
        ),
        Flag(
            id="ireland_multinational_domestic_sector_divergence_flag",
            severity="high" if mne_domestic_fired else "low",
            fired=bool(mne_domestic_fired),
            evidence=(
                f"In {period}, multinational-dominated sectors changed by "
                f"{row['multinational_sector_growth']:.1f}% while domestic sectors changed by "
                f"{row['domestic_sector_growth']:.1f}%. "
                f"Absolute gap = {mne_domestic_gap:.1f} percentage points."
            ),
        ),
        Flag(
            id="ireland_fiscal_concentration_flag",
            severity="high" if fiscal_concentration_fired else "low",
            fired=bool(fiscal_concentration_fired),
            evidence=(
                f"In the first-run fiscal readout, the top three corporate groups account for "
                f"{corp_tax_share:.1f}% of corporation-tax receipts. Threshold = "
                f"{thresholds.ireland_corporation_tax_top_three_share_warning:.1f}%."
            ),
        ),
        Flag(
            id="ireland_dashboard_citizen_divergence_flag",
            severity="not_assessed",
            fired=False,
            evidence=(
                "Not assessed in the first Ireland run. GDP/MDD and fiscal-concentration flags "
                "show measurement and fiscal-risk divergence. Citizen-pressure assessment requires "
                "household-side indicators such as SILC poverty, deprivation, rent burden, residual "
                "income, wage power, or savings resilience."
            ),
        ),
    ]

    notes = [
        "Ireland v0.1 first run separates dashboard distortion from citizen pressure.",
        "GDP/MDD divergence tests measurement distortion, not poverty.",
        "GDP/GNP divergence tests the instability of GDP as a domestic readout.",
        "Corporation-tax concentration tests fiscal fragility, not household poverty.",
        "Citizen pressure is deliberately not inferred from GDP movement alone.",
    ]

    report = CountryReport(
        country="Ireland",
        model_instance="dashboard_domestic_fiscal_first_empirical_run",
        readouts={
            "period": period,
            "gdp_growth": round(float(row["gdp_growth"]), 2),
            "gnp_growth": round(float(row["gnp_growth"]), 2),
            "modified_domestic_demand_growth": round(
                float(row["modified_domestic_demand_growth"]), 2
            ),
            "domestic_sector_growth": round(float(row["domestic_sector_growth"]), 2),
            "multinational_sector_growth": round(float(row["multinational_sector_growth"]), 2),
            "gdp_mdd_gap_points": round(gdp_mdd_gap, 2),
            "gdp_gnp_gap_points": round(gdp_gnp_gap, 2),
            "multinational_domestic_sector_gap_points": round(mne_domestic_gap, 2),
            "corporation_tax_top_three_share": round(corp_tax_share, 2),
            "corporation_tax_top_three_eur_billion": round(
                float(row["corporation_tax_top_three_eur_billion"]), 2
            ),
        },
        flags=flags,
        notes=notes,
        source_registry=source_entries(
            "ireland_cso_qna_q1_2026",
            "ireland_ifac_corporation_tax_2026",
        )
        if "synthetic" not in str(row.get("source_key", ""))
        else [],
    )

    return df, report

# ---------------------------------------------------------------------
# Optional Ireland time-series model
# ---------------------------------------------------------------------

def prepare_ireland_timeseries_data(ireland: pd.DataFrame) -> pd.DataFrame:
    require_columns(ireland, ["year", "gdp", "gni_star", "modified_domestic_demand"], "ireland time series")

    ireland = numeric(
        ireland,
        [
            "year",
            "gdp",
            "gni_star",
            "modified_domestic_demand",
            "at_risk_poverty_rate",
            "enforced_deprivation_rate",
            "median_equivalised_income",
            "rent_burden",
            "house_price_to_income",
        ],
    ).sort_values("year")

    ireland["gdp_to_gni_star_ratio"] = ireland["gdp"] / ireland["gni_star"]

    ireland["gdp_growth"] = ireland["gdp"].pct_change() * 100.0
    ireland["gni_star_growth"] = ireland["gni_star"].pct_change() * 100.0
    ireland["mdd_growth"] = ireland["modified_domestic_demand"].pct_change() * 100.0
    ireland["gdp_minus_mdd_growth_gap"] = ireland["gdp_growth"] - ireland["mdd_growth"]

    base_year = int(ireland["year"].min())
    ireland = index_to_base_year(ireland, "gdp", base_year=base_year, output_col="gdp_index")
    ireland = index_to_base_year(ireland, "gni_star", base_year=base_year, output_col="gni_star_index")
    ireland = index_to_base_year(
        ireland,
        "modified_domestic_demand",
        base_year=base_year,
        output_col="mdd_index",
    )

    return ireland

def run_ireland_timeseries_model(
    ireland: pd.DataFrame,
    thresholds: ModelThresholds,
) -> tuple[pd.DataFrame, CountryReport]:
    df = prepare_ireland_timeseries_data(ireland)

    end_year, latest_ratio = latest_valid(df, "gdp_to_gni_star_ratio")

    growth_gap_valid = df[["year", "gdp_minus_mdd_growth_gap"]].dropna().sort_values("year")
    if growth_gap_valid.empty:
        latest_growth_gap_year = end_year
        latest_growth_gap = 0.0
    else:
        latest_growth_gap_year = int(growth_gap_valid.iloc[-1]["year"])
        latest_growth_gap = float(growth_gap_valid.iloc[-1]["gdp_minus_mdd_growth_gap"])

    flags: list[Flag] = [
        Flag(
            id="ireland_timeseries_gdp_domestic_demand_divergence",
            severity="medium"
            if abs(latest_growth_gap) >= thresholds.ireland_gdp_mdd_growth_gap_points
            else "low",
            fired=bool(abs(latest_growth_gap) >= thresholds.ireland_gdp_mdd_growth_gap_points),
            evidence=(
                f"In {latest_growth_gap_year}, GDP growth minus Modified Domestic Demand growth "
                f"= {latest_growth_gap:.1f} percentage points."
            ),
        )
    ]

    citizen_pressure_fired = False

    if "at_risk_poverty_rate" in df.columns and df["at_risk_poverty_rate"].notna().any():
        poverty_year, poverty_rate = latest_valid(df, "at_risk_poverty_rate")
        fired = poverty_rate >= thresholds.ireland_poverty_rate_warning
        citizen_pressure_fired = citizen_pressure_fired or fired
        flags.append(
            Flag(
                id="ireland_at_risk_poverty_pressure",
                severity="medium" if fired else "low",
                fired=bool(fired),
                evidence=(
                    f"In {poverty_year}, at-risk-of-poverty rate = {poverty_rate:.1f}%. "
                    f"Threshold = {thresholds.ireland_poverty_rate_warning:.1f}%."
                ),
            )
        )

    if "enforced_deprivation_rate" in df.columns and df["enforced_deprivation_rate"].notna().any():
        dep_year, dep_rate = latest_valid(df, "enforced_deprivation_rate")
        fired = dep_rate >= thresholds.ireland_deprivation_warning
        citizen_pressure_fired = citizen_pressure_fired or fired
        flags.append(
            Flag(
                id="ireland_enforced_deprivation_pressure",
                severity="medium" if fired else "low",
                fired=bool(fired),
                evidence=(
                    f"In {dep_year}, enforced-deprivation rate = {dep_rate:.1f}%. "
                    f"Threshold = {thresholds.ireland_deprivation_warning:.1f}%."
                ),
            )
        )

    flags.append(
        Flag(
            id="ireland_timeseries_dashboard_citizen_divergence",
            severity="high" if citizen_pressure_fired else "not_assessed",
            fired=bool(citizen_pressure_fired),
            evidence=(
                "Fires only when citizen-side indicators are present and exceed thresholds. "
                "GDP/GNI*/MDD divergence alone is not treated as proof of poverty."
            ),
        )
    )

    report = CountryReport(
        country="Ireland",
        model_instance="optional_timeseries_model",
        readouts={
            "latest_year": int(end_year),
            "latest_gdp_to_gni_star_ratio": round(latest_ratio, 2),
            "latest_gdp_minus_mdd_growth_gap_year": int(latest_growth_gap_year),
            "latest_gdp_minus_mdd_growth_gap_points": round(latest_growth_gap, 2),
        },
        flags=flags,
        notes=[
            "Optional Ireland time-series model.",
            "Use this when full CSO national-account series and household-side series are available.",
            "GDP/GNI*/MDD divergence alone remains a measurement-distortion flag, not a poverty flag.",
        ],
        source_registry=[],
    )

    return df, report

# ---------------------------------------------------------------------
# Plotting
# ---------------------------------------------------------------------

def plot_canada_capacity(canada: pd.DataFrame, out_dir: Path, synthetic: bool = False) -> None:
    title_suffix = "synthetic demonstration" if synthetic else "first empirical run"

    row = canada.iloc[-1]
    values = [
        row["household_formation"],
        row["net_housing_completions"],
    ]
    labels = [
        "Household formation",
        "Net housing completions",
    ]

    plt.figure(figsize=(10, 6))
    plt.bar(labels, values)
    plt.title(f"Canada v0.1 — Demand versus housing capacity ({title_suffix})")
    plt.ylabel("Units")
    plt.tight_layout()
    plt.savefig(out_dir / "canada_capacity_gap.png", dpi=160)
    plt.close()

    values = [
        row["baseline_projected_net_completions_annual"],
        row["required_net_completions_to_close_gap_annual"],
    ]
    labels = [
        "Baseline projected annual completions",
        "Required annual completions",
    ]

    plt.figure(figsize=(10, 6))
    plt.bar(labels, values)
    plt.title(f"Canada v0.1 — Required build-rate gap ({title_suffix})")
    plt.ylabel("Annual net housing units")
    plt.xticks(rotation=10, ha="right")
    plt.tight_layout()
    plt.savefig(out_dir / "canada_required_build_rate_gap.png", dpi=160)
    plt.close()

def plot_canada_wage(canada: pd.DataFrame, out_dir: Path) -> None:
    plt.figure(figsize=(10, 6))
    plt.plot(canada["year"], canada["gdp_pc_index"], marker="o", label="GDP per capita index")
    plt.plot(canada["year"], canada["real_wage_index"], marker="o", label="Real wage index")
    plt.title("Canada v0.1 — Optional dashboard growth vs wage power")
    plt.xlabel("Year")
    plt.ylabel("Index, first year = 100")
    plt.legend()
    plt.tight_layout()
    plt.savefig(out_dir / "canada_gdp_pc_vs_real_wage.png", dpi=160)
    plt.close()

    plt.figure(figsize=(10, 6))
    plt.plot(
        canada["year"],
        canada["dashboard_minus_wage_gap"],
        marker="o",
        label="GDP-per-capita index minus real-wage index",
    )
    plt.axhline(0, linewidth=1)
    plt.title("Canada v0.1 — Optional dashboard/wage gap")
    plt.xlabel("Year")
    plt.ylabel("Index-point gap")
    plt.legend()
    plt.tight_layout()
    plt.savefig(out_dir / "canada_dashboard_wage_gap.png", dpi=160)
    plt.close()

def plot_ireland_first_run(ireland: pd.DataFrame, out_dir: Path, synthetic: bool = False) -> None:
    title_suffix = "synthetic demonstration" if synthetic else "first empirical run"

    row = ireland.iloc[-1]

    values = [
        row["gdp_growth"],
        row["gnp_growth"],
        row["modified_domestic_demand_growth"],
    ]
    labels = [
        "GDP",
        "GNP",
        "Modified Domestic Demand",
    ]

    plt.figure(figsize=(10, 6))
    plt.bar(labels, values)
    plt.axhline(0, linewidth=1)
    plt.title(f"Ireland v0.1 — Dashboard versus domestic readouts ({title_suffix})")
    plt.ylabel("Quarterly growth, percent")
    plt.tight_layout()
    plt.savefig(out_dir / "ireland_gdp_gnp_mdd_first_run.png", dpi=160)
    plt.close()

    values = [
        row["multinational_sector_growth"],
        row["domestic_sector_growth"],
    ]
    labels = [
        "Multinational-dominated sectors",
        "Domestic sectors",
    ]

    plt.figure(figsize=(10, 6))
    plt.bar(labels, values)
    plt.axhline(0, linewidth=1)
    plt.title(f"Ireland v0.1 — Sector divergence ({title_suffix})")
    plt.ylabel("Quarterly growth, percent")
    plt.xticks(rotation=10, ha="right")
    plt.tight_layout()
    plt.savefig(out_dir / "ireland_sector_divergence_first_run.png", dpi=160)
    plt.close()

    plt.figure(figsize=(10, 6))
    plt.bar(
        ["Top three corporate groups"],
        [row["corporation_tax_top_three_share"]],
    )
    plt.axhline(25.0, linewidth=1, label="v0.1 warning threshold")
    plt.title(f"Ireland v0.1 — Corporation-tax concentration ({title_suffix})")
    plt.ylabel("Share of corporation-tax receipts, percent")
    plt.legend()
    plt.tight_layout()
    plt.savefig(out_dir / "ireland_corporation_tax_concentration.png", dpi=160)
    plt.close()

def plot_ireland_timeseries(ireland: pd.DataFrame, out_dir: Path) -> None:
    plt.figure(figsize=(10, 6))
    plt.plot(ireland["year"], ireland["gdp_index"], marker="o", label="GDP index")
    plt.plot(ireland["year"], ireland["gni_star_index"], marker="o", label="GNI* index")
    plt.plot(ireland["year"], ireland["mdd_index"], marker="o", label="Modified Domestic Demand index")
    plt.title("Ireland v0.1 — Optional GDP/GNI*/MDD time series")
    plt.xlabel("Year")
    plt.ylabel("Index, first year = 100")
    plt.legend()
    plt.tight_layout()
    plt.savefig(out_dir / "ireland_gdp_gni_star_mdd_timeseries.png", dpi=160)
    plt.close()

    citizen_cols = [
        col
        for col in ["at_risk_poverty_rate", "enforced_deprivation_rate"]
        if col in ireland.columns and ireland[col].notna().any()
    ]

    if citizen_cols:
        plt.figure(figsize=(10, 6))
        for col in citizen_cols:
            plt.plot(ireland["year"], ireland[col], marker="o", label=col.replace("_", " "))
        plt.title("Ireland v0.1 — Optional citizen-pressure indicators")
        plt.xlabel("Year")
        plt.ylabel("Percent")
        plt.legend()
        plt.tight_layout()
        plt.savefig(out_dir / "ireland_citizen_pressure_timeseries.png", dpi=160)
        plt.close()

# ---------------------------------------------------------------------
# Synthetic optional data
# ---------------------------------------------------------------------

def synthetic_canada_wage_data() -> pd.DataFrame:
    return pd.DataFrame(
        {
            "year": [1981, 1991, 2001, 2011, 2021, 2024],
            "gdp_per_capita": [100, 118, 139, 156, 171, 175],
            "nominal_wage": [100, 150, 205, 260, 320, 340],
            "cpi": [100, 142, 180, 222, 285, 315],
        }
    )

def synthetic_canada_wage_microdata() -> pd.DataFrame:
    rows = []
    wages_by_year = {
        1981: [8, 9, 10, 10, 11, 12, 13, 15, 20, 70],
        1991: [11, 12, 13, 14, 14, 15, 16, 18, 25, 90],
        2001: [14, 15, 16, 17, 18, 19, 20, 23, 31, 110],
        2011: [17, 18, 19, 20, 21, 22, 24, 28, 39, 150],
        2021: [19, 20, 21, 23, 24, 25, 27, 32, 50, 220],
        2024: [20, 21, 22, 24, 25, 26, 28, 34, 54, 250],
    }
    for year, wages in wages_by_year.items():
        for wage in wages:
            rows.append({"year": year, "wage": wage})
    return pd.DataFrame(rows)

def synthetic_ireland_timeseries_data() -> pd.DataFrame:
    return pd.DataFrame(
        {
            "year": [2015, 2018, 2021, 2024, 2025],
            "gdp": [100, 135, 185, 230, 258],
            "gni_star": [100, 115, 132, 150, 158],
            "modified_domestic_demand": [100, 112, 123, 136, 143],
            "at_risk_poverty_rate": [16.0, 14.0, 11.6, 11.7, 12.6],
            "enforced_deprivation_rate": [25.5, 15.1, 13.8, 15.7, 15.1],
        }
    )

# ---------------------------------------------------------------------
# Post-ready markdown tables
# ---------------------------------------------------------------------

def make_post_tables(
    canada_report: CountryReport,
    ireland_report: CountryReport,
) -> str:
    c = canada_report.readouts
    i = ireland_report.readouts

    canada_table = f"""
### First Canada Run: Demand Versus Capacity

The table below maps the Canada section onto the model's capacity-gap logic. It compares household formation with housing-capacity expansion and converts that comparison into explicit model flags.

| Model test | Public-data readout | Calculation / comparison | Model output |
|---|---:|---:|---|
| Demand pressure | Household formation | {c["household_formation"]:,} net new households | demand_pressure_input = high |
| Capacity response | Net housing completions | {c["net_housing_completions"]:,} net units | capacity_response_input = constrained |
| Capacity gap | Household formation minus net completions | {c["capacity_gap_units"]:,} units | canada_capacity_gap_flag = fired |
| Demand/capacity ratio | Household formation divided by net completions | {c["demand_to_completion_ratio"]:.2f}x | capacity_gap_pressure = high |
| Required build-rate gap | Required annual completions minus projected baseline annual completions | {c["required_build_rate_gap_units"]:,} units per year | canada_required_build_rate_gap_flag = fired |
""".strip()

    ireland_table = f"""
### First Ireland Run: Dashboard Versus Domestic Readout

The table below maps the Ireland section onto the model's measurement-distortion and fiscal-concentration logic. It does not infer citizen poverty from GDP. It tests whether headline GDP diverges from domestic-capacity measures and whether apparent fiscal strength carries concentration risk.

| Model test | Public-data readout | Calculation / comparison | Model output |
|---|---:|---:|---|
| GDP versus domestic demand | GDP growth versus Modified Domestic Demand growth | {i["gdp_growth"]:.1f}% versus {i["modified_domestic_demand_growth"]:.1f}% | ireland_gdp_mdd_divergence_flag = fired |
| GDP versus GNP | GDP growth versus GNP growth | {i["gdp_growth"]:.1f}% versus {i["gnp_growth"]:.1f}% | ireland_gdp_gnp_divergence_flag = fired |
| Multinational/domestic split | Multinational-dominated sectors versus domestic sectors | {i["multinational_sector_growth"]:.1f}% versus {i["domestic_sector_growth"]:.1f}% | ireland_multinational_domestic_sector_divergence_flag = fired |
| Fiscal concentration | Top three corporate groups' share of corporation-tax receipts | {i["corporation_tax_top_three_share"]:.1f}% | ireland_fiscal_concentration_flag = fired |
| Citizen pressure | Household-side indicators required | Not inferred from GDP alone | ireland_dashboard_citizen_divergence_flag = not assessed |
""".strip()

    return canada_table + "\n\n" + ireland_table + "\n"

# ---------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------

def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Citizen Solvency Model v0.1 — first empirical appendix run"
    )

    parser.add_argument("--canada-capacity", type=Path, help="Path to Canada capacity CSV")
    parser.add_argument("--ireland-first-run", type=Path, help="Path to Ireland first-run CSV")

    parser.add_argument("--canada-wage", type=Path, help="Optional Canada wage/dashboard CSV")
    parser.add_argument(
        "--canada-wage-microdata",
        type=Path,
        help="Optional Canada wage microdata CSV",
    )
    parser.add_argument(
        "--ireland-timeseries",
        type=Path,
        help="Optional Ireland GDP/GNI*/MDD time-series CSV",
    )

    parser.add_argument(
        "--synthetic",
        action="store_true",
        help="Run with synthetic constructed data instead of first-run public values",
    )

    parser.add_argument(
        "--out",
        type=Path,
        default=Path("reports/csm_v01"),
        help="Output directory",
    )

    return parser.parse_args()

def main() -> None:
    args = parse_args()
    out_dir = ensure_out_dir(args.out)
    thresholds = ModelThresholds()

    synthetic_mode = bool(args.synthetic)

    # Canada capacity model
    if args.canada_capacity:
        canada_capacity = pd.read_csv(args.canada_capacity)
    elif synthetic_mode:
        canada_capacity = synthetic_canada_capacity_data()
    else:
        canada_capacity = first_run_canada_capacity_data()

    canada_capacity_model, canada_capacity_report = run_canada_capacity_model(
        canada_capacity,
        thresholds,
    )

    # Ireland first-run model
    if args.ireland_first_run:
        ireland_first_run = pd.read_csv(args.ireland_first_run)
    elif synthetic_mode:
        ireland_first_run = synthetic_ireland_data()
    else:
        ireland_first_run = first_run_ireland_data()

    ireland_first_run_model, ireland_first_run_report = run_ireland_first_run_model(
        ireland_first_run,
        thresholds,
    )

    reports: list[CountryReport] = [
        canada_capacity_report,
        ireland_first_run_report,
    ]

    # Optional Canada wage/dashboard model
    canada_wage_model = None
    canada_wage_report = None

    if args.canada_wage or synthetic_mode:
        canada_wage = (
            pd.read_csv(args.canada_wage)
            if args.canada_wage
            else synthetic_canada_wage_data()
        )

        canada_micro = None
        if args.canada_wage_microdata:
            canada_micro = pd.read_csv(args.canada_wage_microdata)
        elif synthetic_mode:
            canada_micro = synthetic_canada_wage_microdata()

        canada_wage_model, canada_wage_report = run_canada_wage_model(
            canada_wage,
            thresholds,
            wage_microdata=canada_micro,
        )
        reports.append(canada_wage_report)

    # Optional Ireland time-series model
    ireland_timeseries_model = None
    ireland_timeseries_report = None

    if args.ireland_timeseries or synthetic_mode:
        ireland_timeseries = (
            pd.read_csv(args.ireland_timeseries)
            if args.ireland_timeseries
            else synthetic_ireland_timeseries_data()
        )

        ireland_timeseries_model, ireland_timeseries_report = run_ireland_timeseries_model(
            ireland_timeseries,
            thresholds,
        )
        reports.append(ireland_timeseries_report)

    # Write model outputs
    canada_capacity_model.to_csv(out_dir / "canada_capacity_model_output.csv", index=False)
    ireland_first_run_model.to_csv(out_dir / "ireland_first_run_model_output.csv", index=False)

    write_report(canada_capacity_report, out_dir / "canada_capacity_report.json")
    write_report(ireland_first_run_report, out_dir / "ireland_first_run_report.json")

    if canada_wage_model is not None and canada_wage_report is not None:
        canada_wage_model.to_csv(out_dir / "canada_wage_model_output.csv", index=False)
        write_report(canada_wage_report, out_dir / "canada_wage_report.json")

    if ireland_timeseries_model is not None and ireland_timeseries_report is not None:
        ireland_timeseries_model.to_csv(out_dir / "ireland_timeseries_model_output.csv", index=False)
        write_report(ireland_timeseries_report, out_dir / "ireland_timeseries_report.json")

    # Combined flags
    flag_frames = []
    for report in reports:
        frame = flags_to_frame(report)
        frame["country"] = report.country
        frame["model_instance"] = report.model_instance
        flag_frames.append(frame)

    all_flags = pd.concat(flag_frames, ignore_index=True)
    all_flags.to_csv(out_dir / "all_flags.csv", index=False)

    # Plots
    plot_canada_capacity(canada_capacity_model, out_dir, synthetic=synthetic_mode)
    plot_ireland_first_run(ireland_first_run_model, out_dir, synthetic=synthetic_mode)

    if canada_wage_model is not None:
        plot_canada_wage(canada_wage_model, out_dir)

    if ireland_timeseries_model is not None:
        plot_ireland_timeseries(ireland_timeseries_model, out_dir)

    # Post-ready markdown tables
    post_tables = make_post_tables(
        canada_capacity_report,
        ireland_first_run_report,
    )
    (out_dir / "post_tables.md").write_text(post_tables, encoding="utf-8")

    # Source registry
    write_json(SOURCE_REGISTRY, out_dir / "source_registry.json")

    summary = {
        "model": "citizen_solvency_model_v0.1",
        "mode": "synthetic_demonstration" if synthetic_mode else "first_empirical_run",
        "thresholds": asdict(thresholds),
        "reports": [asdict(r) for r in reports],
        "outputs": [
            str(out_dir / "canada_capacity_model_output.csv"),
            str(out_dir / "ireland_first_run_model_output.csv"),
            str(out_dir / "canada_capacity_report.json"),
            str(out_dir / "ireland_first_run_report.json"),
            str(out_dir / "all_flags.csv"),
            str(out_dir / "post_tables.md"),
            str(out_dir / "source_registry.json"),
            str(out_dir / "canada_capacity_gap.png"),
            str(out_dir / "canada_required_build_rate_gap.png"),
            str(out_dir / "ireland_gdp_gnp_mdd_first_run.png"),
            str(out_dir / "ireland_sector_divergence_first_run.png"),
            str(out_dir / "ireland_corporation_tax_concentration.png"),
        ],
    }

    if canada_wage_model is not None:
        summary["outputs"].extend(
            [
                str(out_dir / "canada_wage_model_output.csv"),
                str(out_dir / "canada_wage_report.json"),
                str(out_dir / "canada_gdp_pc_vs_real_wage.png"),
                str(out_dir / "canada_dashboard_wage_gap.png"),
            ]
        )

    if ireland_timeseries_model is not None:
        summary["outputs"].extend(
            [
                str(out_dir / "ireland_timeseries_model_output.csv"),
                str(out_dir / "ireland_timeseries_report.json"),
                str(out_dir / "ireland_gdp_gni_star_mdd_timeseries.png"),
            ]
        )

    write_json(summary, out_dir / "summary.json")

    print(json.dumps(summary, indent=2))
    print()
    print("Post-ready tables written to:", out_dir / "post_tables.md")

if __name__ == "__main__":
    main()

Appendix B: AI Audit Layer Demonstration

Appendix A shows that the Citizen Solvency Model can be expressed as code.

This appendix shows what the AI layer contributes.

The AI layer is not used as an oracle. It does not decide whether the thesis is true. It does not replace data, economics, or judgement.

Its narrower role is to turn prose into inspectable model objects.

In the main post, that means taking a paragraph that contains economic language and asking:

What claims are being made?
What variables are implied?
Which terms are ambiguous?
Which claims require sources?
Which relationships are structural?
Which outputs are merely readouts?
What would falsify the claim?

That is the AI contribution: decomposition, not authority.

Example Input Paragraph

The following paragraph is representative of the kind of prose the AI audit layer is meant to inspect:

Headline GDP is unusually sensitive to multinational activity. That does not mean Ireland is fake. It does not mean the activity is imaginary. It means GDP is too volatile and too globally mediated to serve, by itself, as the readout for Irish citizen solvency.

A normal essay might leave that paragraph as prose.

The AI audit layer turns it into an audit object.

AI Audit Output

ai_audit_example:
  source_paragraph:
    id: ireland_gdp_measurement_claim
    text: >
      Headline GDP is unusually sensitive to multinational activity.
      That does not mean Ireland is fake. It does not mean the activity
      is imaginary. It means GDP is too volatile and too globally mediated
      to serve, by itself, as the readout for Irish citizen solvency.

  extracted_claims:
    - id: claim_001
      claim: "Irish headline GDP is unusually sensitive to multinational activity."
      type: empirical
      requires_source: true
      suggested_sources:
        - "CSO national accounts"
        - "CSO modified GNI documentation"
        - "Central Bank of Ireland commentary"
      uncertainty: "Magnitude and time period must be specified."

    - id: claim_002
      claim: "Multinational-linked GDP activity is real but not equivalent to domestic citizen capacity."
      type: measurement_distinction
      requires_source: true
      suggested_sources:
        - "CSO GDP / GNI* comparison"
        - "Modified Domestic Demand series"
      uncertainty: "Needs careful wording to avoid implying the activity is fake."

    - id: claim_003
      claim: "GDP alone is not a sufficient citizen-solvency readout for Ireland."
      type: model_claim
      requires_source: false
      requires_model_definition: true
      uncertainty: "Depends on how citizen solvency is defined."

  ambiguous_terms:
    - term: "sensitive"
      problem: "Could mean level distortion, volatility, or both."
      required_clarification: "Specify whether the claim concerns GDP level, GDP growth volatility, or GDP/domestic-demand divergence."

    - term: "multinational activity"
      problem: "Too broad."
      required_clarification: "Separate multinational profits, exports, intellectual-property relocation, contract manufacturing, corporation-tax receipts, and employment."

    - term: "citizen solvency"
      problem: "Model-defined term."
      required_clarification: "Must map to household-side indicators such as rent burden, real wage power, savings resilience, deprivation, ownership access, and public-service capacity."

  variable_candidates:
    dashboard_variables:
      - headline_gdp
      - gdp_growth
      - gdp_per_capita
      - corporation_tax_receipts
      - export_growth
      - multinational_profit_share

    domestic_capacity_variables:
      - gni_star
      - modified_domestic_demand
      - domestic_income
      - domestic_employment
      - domestic_productivity
      - domestic_capital_formation

    citizen_solvency_variables:
      - rent_burden
      - house_price_to_income
      - median_equivalised_income
      - at_risk_poverty_rate
      - enforced_deprivation_rate
      - savings_resilience
      - ownership_access_by_age_cohort

  structural_layer:
    nodes:
      - multinational_profit_booking
      - export_volatility
      - corporation_tax_concentration
      - domestic_wage_power
      - housing_costs
      - public_service_capacity

  measurement_layer:
    readouts:
      - headline_gdp
      - gni_star
      - modified_domestic_demand
      - citizen_solvency_dashboard

  possible_edges:
    - from: multinational_profit_booking
      to: headline_gdp
      type: measurement_effect
      evidence_required: true

    - from: corporation_tax_concentration
      to: fiscal_fragility
      type: structural_risk
      evidence_required: true

    - from: housing_costs
      to: citizen_solvency_dashboard
      type: measurement_component
      warning: "Do not draw this as causation if housing_costs are part of the readout."

  required_evidence:
    - claim: "GDP diverges from domestic-capacity measures."
      data_needed:
        - headline_gdp
        - gni_star
        - modified_domestic_demand
      output:
        - gdp_distortion_flag
        - domestic_capacity_divergence_flag

    - claim: "Citizen solvency may not track headline GDP."
      data_needed:
        - median income
        - rent burden
        - poverty or deprivation rates
        - ownership access
      output:
        - dashboard_citizen_divergence_flag

    - claim: "Fiscal strength may hide concentration risk."
      data_needed:
        - corporation_tax_receipts
        - concentration by firm or sector
        - windfall classification
      output:
        - fiscal_concentration_flag

  falsification_tests:
    - test: "GDP and GNI* move together over the relevant period."
      implication: "Weakens GDP-distortion claim."

    - test: "Modified Domestic Demand improves alongside citizen-solvency indicators."
      implication: "Weakens dashboard/citizen divergence claim."

    - test: "Corporation-tax receipts are broad-based and stable."
      implication: "Weakens fiscal-concentration claim."

    - test: "Household indicators improve despite GDP volatility."
      implication: "Shows GDP volatility does not necessarily imply citizen pressure."

  audit_flags:
    - id: source_required
      reason: "Empirical claims require CSO / Central Bank / fiscal sources."

    - id: avoid_fake_gdp_language
      reason: "The prose must not imply multinational activity is imaginary."

    - id: distinguish_distortion_direction
      reason: "GDP can exaggerate domestic strength or apparent weakness."

    - id: citizen_pressure_not_proven_by_gdp
      reason: "Citizen pressure must be measured from household-side data, not inferred from GDP/GNI* alone."

  model_response:
    - "Treat GDP as a dashboard input, not the citizen-solvency readout."
    - "Compare GDP with GNI* and Modified Domestic Demand."
    - "Require separate household-side indicators before firing citizen-pressure flags."
    - "Mark unsupported empirical claims as source-required."

What This Demonstrates

This is the part ordinary prose does not reliably do.

The AI layer extracts hidden claims, separates measurement from causation, identifies variables, names missing evidence, proposes falsification tests, and flags where the author might accidentally overstate the claim.

The AI output is not accepted automatically.

It becomes a checklist.

A claim still needs a source.

A variable still needs a definition.

A relationship still needs a mechanism.

A threshold still needs justification.

A conclusion still needs a falsification test.

The AI contribution is therefore not that it “knows” the answer. It is that it makes the author’s claims harder to hide inside fluent prose.

In the full model, this audit layer should produce a claim-to-source ledger:

claim
    -> variable
    -> source required
    -> model layer
    -> output flag
    -> falsification test
    -> uncertainty status

That is the technical contribution.

The AI does not become the authority.

It becomes the adversarial clerk.


Appendix C: Null Case Demonstration

A model that only finds failure is not a model.

It is a bias engine.

The Citizen Solvency Model therefore needs a null case: a scenario where the national dashboard improves and citizen solvency improves with it.

In that case, the model should not force the thesis.

It should clear its own warnings.

This appendix demonstrates that behaviour using a deliberately simple synthetic case.

The point is not to claim that this exact country exists.

The point is to show that the model can return:

real growth
no capacity-gap flag
no dashboard/citizen divergence flag
no dependency-pressure flag

Null Case Scenario

In this scenario:

GDP rises.
GDP per capita rises.
Real wages rise.
Rent burden falls.
Housing completions keep pace with household formation.
Savings resilience improves.
Ownership access improves.
Public-service capacity improves.
Debt rises, but public capital rises with it.

That is the model’s clean success case.

The dashboard improves.

The citizen balance sheet improves.

The future citizen inherits capacity alongside burden.

The correct output is not warning.

The correct output is clearance.

Synthetic Input

The null case is synthetic. It proves that the model can clear its own warnings when dashboard and citizen-side indicators improve together. It is not yet a historical country case.

year,gdp_index,gdp_per_capita_index,real_wage_index,rent_burden,household_formation_index,housing_completion_index,savings_resilience_index,ownership_access_index,public_capital_index,debt_service_index
2020,100,100,100,35,100,100,100,100,100,100
2021,103,102,102,34,102,103,103,101,104,102
2022,107,105,105,32,104,106,107,103,110,104
2023,112,109,109,30,106,110,112,106,117,106
2024,118,114,115,28,108,115,118,110,125,108

Expected Model Reading

null_case_reading:
  dashboard:
    gdp_index:
      start: 100
      end: 118
      direction: improved

    gdp_per_capita_index:
      start: 100
      end: 114
      direction: improved

  citizen:
    real_wage_index:
      start: 100
      end: 115
      direction: improved

    rent_burden:
      start: 35
      end: 28
      direction: improved

    savings_resilience_index:
      start: 100
      end: 118
      direction: improved

    ownership_access_index:
      start: 100
      end: 110
      direction: improved

  capacity:
    household_formation_index:
      start: 100
      end: 108

    housing_completion_index:
      start: 100
      end: 115

    capacity_gap:
      status: cleared
      reason: "Housing completions rose faster than household formation."

  future_citizen:
    debt_service_index:
      start: 100
      end: 108

    public_capital_index:
      start: 100
      end: 125

    transfer_classification:
      status: productive_transfer
      reason: "Debt burden rose, but public capital rose faster."

  model_output:
    real_growth: true
    dashboard_growth_only: false
    capacity_gap_flag: false
    dashboard_citizen_divergence_flag: false
    dependency_pressure_flag: false
    productive_transfer_flag: true

Minimal Python Test

import pandas as pd

data = pd.DataFrame(
    {
        "year": [2020, 2021, 2022, 2023, 2024],
        "gdp_index": [100, 103, 107, 112, 118],
        "gdp_per_capita_index": [100, 102, 105, 109, 114],
        "real_wage_index": [100, 102, 105, 109, 115],
        "rent_burden": [35, 34, 32, 30, 28],
        "household_formation_index": [100, 102, 104, 106, 108],
        "housing_completion_index": [100, 103, 106, 110, 115],
        "savings_resilience_index": [100, 103, 107, 112, 118],
        "ownership_access_index": [100, 101, 103, 106, 110],
        "public_capital_index": [100, 104, 110, 117, 125],
        "debt_service_index": [100, 102, 104, 106, 108],
    }
)

def direction(series):
    return series.iloc[-1] - series.iloc[0]

dashboard_improved = (
    direction(data["gdp_index"]) > 0
    and direction(data["gdp_per_capita_index"]) > 0
)

citizen_improved = (
    direction(data["real_wage_index"]) > 0
    and direction(data["savings_resilience_index"]) > 0
    and direction(data["ownership_access_index"]) > 0
    and direction(data["rent_burden"]) < 0
)

capacity_scaled = (
    direction(data["housing_completion_index"])
    >= direction(data["household_formation_index"])
)

future_capacity_exceeds_debt = (
    direction(data["public_capital_index"])
    > direction(data["debt_service_index"])
)

result = {
    "real_growth": dashboard_improved and citizen_improved,
    "dashboard_growth_only": dashboard_improved and not citizen_improved,
    "capacity_gap_flag": not capacity_scaled,
    "dashboard_citizen_divergence_flag": dashboard_improved and not citizen_improved,
    "dependency_pressure_flag": not citizen_improved,
    "productive_transfer_flag": future_capacity_exceeds_debt,
}

print(result)

Expected Output

{
  "real_growth": true,
  "dashboard_growth_only": false,
  "capacity_gap_flag": false,
  "dashboard_citizen_divergence_flag": false,
  "dependency_pressure_flag": false,
  "productive_transfer_flag": true
}

Why This Matters

The null case is a discipline check.

It proves that the model does not have to find citizen insolvency.

If dashboard growth is matched by stronger real wages, lower rent burden, better savings resilience, improved ownership access, adequate housing supply, and rising public capital, the model clears its warnings.

That matters because the thesis is not:

Growth is fake.
Debt is bad.
Population growth is harmful.
The dashboard always lies.

The thesis is narrower:

Dashboard improvement is not enough.
The citizen readout has to improve too.

In the null case, it does.

So the model accepts the result.

The warning flags clear.


References and Evidence Map

The references below are not decorative. They are the source base for the model’s main evidence requirements: dashboard measures, domestic-capacity measures, citizen-solvency indicators, fiscal concentration, external constraints, and AI audit discipline.

Where possible, the table prioritizes official statistical agencies, fiscal institutions, central banks, international organizations, and primary-data sources.

Model area Source What it supports / proves Use in this post
Ireland measurement correction CSO — Modified GNI Modified GNI/GNI* is an Ireland-specific measure designed to exclude globalisation effects from national income. Supports the claim that Irish headline GDP cannot be treated as the citizen-solvency readout by itself.
Ireland GDP/GNI*/domestic divergence CSO — Quarterly National Accounts Q1 2026 Q1 2026 showed GDP falling sharply while GNP and Modified Domestic Demand moved differently. Supports the claim that Irish GDP can diverge from domestic-capacity readouts in either direction.
Ireland GDP volatility / MNE distortion CSO — Q1 2026 Key Findings Multinational-dominated sectors contracted sharply while domestic sectors expanded slightly. Supports the Ireland section’s revised framing: GDP is volatile and globally mediated, not simply “fake” or always overstated.
Ireland de-globalised national accounts CSO — Annual National Accounts: GNI* and De-Globalised Results Shows GNI* and de-globalised measures as part of Ireland’s national accounts framework. Provides source base for the GDP/GNI*/domestic-capacity comparison.
Ireland GDP/GNI* policy explanation Department of Finance — GDP and Modified GNI Explanatory Note Explains why Ireland uses modified indicators to control for globalisation effects in macro statistics. Supports the claim that GDP is valid for some questions but miscalibrated for citizen-solvency analysis.
Ireland corporation-tax concentration Irish Fiscal Advisory Council — Three firms account for almost half of corporation tax revenues Estimates that the top three corporate groups accounted for 46% of Irish corporation-tax revenues in 2024. Supports the fiscal-concentration flag and the claim that apparent fiscal strength can hide concentration risk.
Ireland fiscal risk / corporation tax IFAC — Fiscal Assessment Report June 2026 Discusses corporation-tax receipts, spending plans, and fiscal risk. Supports the future-citizen risk around converting volatile receipts into ongoing commitments.
Ireland fiscal stance / excess corporation tax IFAC — Fiscal Assessment Report June 2025 Discusses foreign-owned multinational corporation tax and underlying fiscal balance concepts. Supports the claim that corporation-tax windfalls need to be separated from durable domestic revenue.
Ireland domestic economy / MDD Central Bank of Ireland — Quarterly Bulletin Q2 2026 Discusses Modified Domestic Demand and domestic resilience indicators. Supports the distinction between headline GDP and underlying domestic economic conditions.
Ireland GDP shock / euro area effect Central Bank of Ireland — Monetary Policy and Economic Outlook speech, June 2026 Notes Ireland’s Q1 2026 GDP decline and its multinational-accounting role. Supports the argument that Irish headline GDP can move euro-area aggregates while not directly describing domestic citizen conditions.
Ireland poverty / household pressure CSO — Survey on Income and Living Conditions 2024 Provides median disposable income, at-risk-of-poverty, consistent poverty, and cost-of-living-related poverty figures. Supplies citizen-side indicators for Ireland; prevents the model from inferring poverty from GDP/GNI* alone.
Ireland deprivation CSO — SILC Enforced Deprivation 2024 Provides enforced deprivation rates and deprivation-item data. Supports the citizen-pressure flag in the Ireland model instance.
Ireland poverty detail CSO — SILC 2024 Poverty Breaks down at-risk-of-poverty and deprivation overlap. Supports the claim that citizen pressure must be measured through household-side data, not headline GDP.
Canada GDP per capita Statistics Canada — Table 36-10-0706-01: GDP per capita and other per-capita macroeconomic indicators Official data for Canadian GDP per capita and related per-capita macro indicators. Supports the Canada dashboard/citizen divergence test.
Canada GDP-per-capita context Statistics Canada — GDP per capita, quality-of-life indicator Provides recent real GDP-per-capita figures and source table reference. Supports charting Canada’s per-capita growth rather than aggregate-only GDP.
Canada long-run GDP-per-capita analysis Statistics Canada — Canada’s gross domestic product per capita: Perspectives on the return to trend Discusses long-run Canadian real GDP per capita since 1981. Supports the model’s long-run dashboard-side Canada comparison.
Canada population growth Statistics Canada — Table 17-10-0009-01: Population estimates, quarterly Official quarterly population estimates. Supplies the demand-growth side of the population-capacity test.
Canada wages Statistics Canada — Table 14-10-0222-01: Average hourly and weekly earnings Official wage and earnings data. Supplies the wage-power side of the Canada citizen-solvency readout.
Canada wage quality / distribution context Statistics Canada — Quality of Employment in Canada: Average earnings, 2024 Provides wage data and distributional context by group. Helps avoid overclaiming that all Canadians are poorer; supports a more precise wage-power analysis.
Canada CPI / real wage adjustment Bank of Canada — Consumer Price Index Defines CPI and its use in tracking consumer-price changes. Supports CPI-adjusting wage data in the appendix model.
Canada housing completions / starts CMHC — Monthly Housing Starts and Other Construction Data Tables Official housing starts, completions, construction, and absorption data. Supplies the housing-capacity side of the Canada population-capacity gap.
Canada housing data hub CMHC — Housing data: latest statistics and trends CMHC housing-market, mortgage, rental, and debt data. General source base for Canada housing-access and rent-burden indicators.
Canada household formation vs housing stock Parliamentary Budget Officer — Household Formation and the Housing Stock Estimates that household formation surged above housing completions in 2023. Directly supports the population-capacity-gap chart for Canada.
Canada housing affordability OECD Economic Surveys: Canada 2025 — Improving housing affordability Describes Canada’s housing affordability problem and supply not keeping pace with demand. Supports Canada as the capacity-gap case.
International housing affordability OECD Affordable Housing Database Provides internationally comparable housing affordability, housing-condition, and policy indicators. Supports rent burden, housing-cost-over-income, and international comparison metrics.
House-price-to-income OECD — Housing Prices indicator Defines price-to-income and price-to-rent ratios. Supports the model’s use of house-price-to-income as an ownership-access pressure measure.
Canada ownership by generation Statistics Canada — Millennials in the Canadian housing market Compares millennial homeownership with earlier cohorts. Supports the entrant-exclusion and ownership-access-by-age-cohort indicators.
Canada household debt Bank of Canada — Financial Stability Report 2025 Discusses household debt relative to disposable income and financial-system vulnerability. Supports household-debt and resilience indicators in the Canada citizen-solvency readout.
Canada household vulnerability Bank of Canada — Financial Stability Report 2026: Households Discusses elevated household debt and vulnerability to shocks. Supports the model’s household-resilience and debt-service-pressure variables.
Canada productivity / construction capacity Statistics Canada — Firm size and labour productivity growth in Canadian residential construction Discusses low productivity growth in residential construction over several decades. Supports the construction-throughput/productivity bottleneck in the population-capacity model.
Eurozone monetary constraint ECB — Key ECB interest rates Official source for euro-area policy rates. Supports the claim that Ireland does not set monetary policy around Irish domestic conditions alone.
Irish ECB rate channel Central Bank of Ireland — ECB Interest Rates Irish central-bank presentation of current ECB rates. Supports the external-constraint layer for Irish mortgage, credit, and fiscal conditions.
EU fiscal rules / fiscal constraint European Commission — New Economic Governance Framework Describes the EU fiscal-governance framework that entered into force in 2024. Supports the claim that Irish fiscal room operates inside EU/eurozone rules.
Fiscal governance database European Commission — Fiscal Governance Database Tracks fiscal rules, medium-term frameworks, and independent fiscal institutions across EU states. Supports the external-constraint layer and fiscal-policy-room discussion.
Ireland macro/fiscal assessment IMF — Ireland 2025 Article IV Consultation IMF assessment of Ireland’s macroeconomic and fiscal position. Supports the Ireland section’s treatment of domestic growth, uncertainty, housing, and fiscal policy.
AI risk management NIST — AI Risk Management Framework 1.0 Provides trustworthy-AI principles including validity, reliability, accountability, transparency, and risk management. Supports the AI audit layer’s constraints against hallucination, false precision, and untraceable outputs.
Generative AI risk NIST — Generative AI Profile / AI RMF companion Discusses generative AI risks and trustworthy-AI characteristics. Supports the post’s AI-risk section: hallucination, source laundering, false precision, and auditability.
AI in government / policy evaluation OECD — Governing with Artificial Intelligence: AI in policy evaluation Discusses AI’s role in supporting policy evaluation. Supports the claim that AI can help decompose policy claims, map evidence, and support evaluation when bounded.
AI public-sector accountability OECD — Governing with Artificial Intelligence Frames public-sector AI around productivity, responsiveness, accountability, and data prerequisites. Supports AI as audit sidecar / model tooling rather than oracle.
Responsible AI principles OECD — AI Principles Establishes AI principles around trustworthy, human-centred, accountable AI. Supports the model’s requirement that AI outputs be bound to sources, assumptions, tests, or uncertainty.
Measurement-target failure Goodhart’s Law — Cambridge summary States the core warning that when a measure becomes a target, it ceases to be a good measure. Supports the Dashboard State / objective-function argument.
Measurement and optimization Goodhart’s Law overview Provides background on Goodhart’s Law and its origins. Secondary reference for the measurement-crisis / objective-function-crisis framing.
Formal Goodhart analysis El-Mhamdi & Hoang — On Goodhart’s Law, with an application to value alignment Formalizes Goodhart effects under optimization pressure. Optional technical reference linking dashboard optimization to AI/model-alignment language.
Household cost burden comparison OECD — Housing Cost Over Income Defines housing-cost burden and cross-country housing-cost-over-income indicators. Supports the citizen-solvency dashboard’s rent burden / housing cost burden variables.
International housing pressure context IMF Finance & Development — Housing Costs Mount Discusses generational housing-affordability pressure across countries. Contextual support for the claim that housing access is a citizen-solvency and intergenerational issue.
Goodhart taxonomy / metric overoptimization Manheim & Garrabrant — Categorizing Variants of Goodhart’s Law Formalizes Goodhart’s Law as a family of distinct failure modes caused by overoptimizing proxies. Supports the claim that dashboard optimization can improve measured variables while degrading the underlying target.
AI reward misspecification / specification gaming DeepMind — Specification gaming: the flip side of AI ingenuity Shows how an optimizer can satisfy the literal reward specification while missing the intended outcome. Supports the analogy between AI reward misspecification and state dashboard optimization, while keeping the claim structural rather than literal.
AI reward hacking examples Victoria Krakovna — Specification gaming examples in AI Collects examples of AI systems exploiting proxy objectives or loopholes in task specifications. Supports the post’s claim that proxy optimization can produce formally successful but substantively wrong behaviour.
State legibility / administrative simplification James C. Scott — Seeing Like a State Classic account of how states simplify complex social realities into legible administrative categories, sometimes damaging the systems they intend to improve. Supports the “Dashboard State” framing: the state sees what it can measure and may optimize legibility over lived reality.
Pareto efficiency / trade-off framing Stanford Encyclopedia of Philosophy — Philosophy of Economics, Pareto efficiency discussion Defines Pareto efficiency as a state where no one can be made better off without making someone else worse off. Supports the point that policy choices involve trade-offs and cannot be reduced to one maximized dashboard variable.
Multi-objective optimization Kalyanmoy Deb — Multi-Objective Optimization Using Evolutionary Algorithms Standard reference for optimization across multiple objectives and trade-off surfaces. Supports the Citizen Solvency Model’s framing as a constrained multi-objective optimizer rather than a single-score maximizer.
Multi-objective / Pareto-front tutorial Emmerich & Deutz — A tutorial on multiobjective optimization Provides a technical overview of multi-objective optimization and Pareto-front reasoning. Supports the model’s use of competing readouts, constraints, and policy trade-off surfaces.