Sizing the Viability Gap Fund: An Iterative Optimisation Framework for PPP Feasibility

A decision-support family for the smallest public subsidy a PPP needs — single-objective sizing, multi-objective Pareto fronts, robust optimisation under stress, and continuous risk-allocation search

Author: David Duarte Arancibia, Austral Infrastructure Fiscal Intelligence Date: April 27, 2026 Audience: Ministry of Finance officials, sector regulators, PPP units, MDB advisors, finance practitioners Companion software: Austral platform — https://platform.austral-intelligence.com


Abstract

Public–Private Partnerships (PPPs) typically require some form of public support — most commonly a Viability Gap Fund (VGF), an upfront grant sized as a percentage of capital expenditure — to be simultaneously bankable for the private sector, attractive on a Value-for-Money (VfM) basis, and affordable for the government over the contract term. Practitioners size that subsidy by hand, walking sequentially through risk allocation, cost-of-equity calculation, VfM testing, financial-model bankability, and fiscal-affordability testing — and iterating only when one of those gates obviously fails.

This paper formalises the sizing problem as a constrained optimisation over the project's risk allocation and financial structure, with the VGF percentage as the principal sizing knob and bankability + VfM + affordability as the binding constraints. We describe a family of seven complementary search modes — three single-objective variants (min_vgf, min_fiscal_npv, max_irr_within_envelope), a multi-objective Pareto front via NSGA-II (the Non-dominated Sorting Genetic Algorithm II), robust optimisation under a stress catalogue, continuous risk-allocation search in 13 dimensions on the NSGA-II Pareto front (with a 4-dimensional financial-structure search via differential evolution, DE, as a separate mode), and a Bayesian-optimisation alternative — all sharing the same financial-model engine, the same per-jurisdiction FCCL aggregation, and the same tamper-evident audit chain.

The contribution is fourfold: (i) a precise statement of the sizing problem with its design variables and gates; (ii) a reproducible reference implementation that any practitioner can run in ten minutes against a worked Ghanaian PPP example (the Accra–Tema Motorway Extension), with signed PDF reports for review by Supreme Audit Institutions; (iii) a transparent treatment of the iteration logic — including how the optimiser handles infeasible regions, multi-jurisdiction fiscal splits, multi-currency exposure with per-year FX paths, and stress catalogues; and (iv) a decision-support family that lets the practitioner ask the optimiser the question that matches the actual decision: "smallest grant", "smallest lifetime cost", "most attractive bid within an envelope", "smallest grant under stress", or "what does the trade-off curve look like".

Keywords: Public–Private Partnership, Viability Gap Funding, fiscal commitments and contingent liabilities (FCCL), value for money, bankability, optimisation, multi-objective Pareto front, robust optimisation, audit-evidence.

Feedback & discussion. This paper is published openly so that practitioners — Ministry of Finance staff, sector regulators, MDB advisors, lenders, academics — can challenge it, refine it, and put it to work on real projects. We welcome corrections, counter-examples, and proposed extensions on any section.


1. Executive summary

Designing a Public–Private Partnership (PPP) is an iterative problem disguised as a sequential one. Practitioners typically run five analyses — risk allocation, cost of equity, value-for-money, financial model bankability, and fiscal affordability — and treat them as hand-offs. In practice, each downstream stage feeds back into the upstream one: an unattractive Value-for-Money (VfM) result demands a different risk allocation; an unbankable financial model demands more public support; an unaffordable fiscal exposure demands a smaller project.

The single number that connects all of these together is the Viability Gap Fund (VGF) — the upfront grant the government commits, expressed as a percentage of capex. Almost every other lever (risk allocation, concessional terms) ultimately influences how big the VGF must be. The right framing of PPP feasibility is therefore: what is the smallest VGF that simultaneously satisfies bankability, value-for-money, and fiscal-affordability gates, given the project's design and a defensible risk allocation?

For the worked example in this paper — the Accra–Tema Motorway, a $620M user-pays toll concession — the answer the optimiser returns is VGF = 0%: the project is bankable with no subsidy, placing effectively zero direct fiscal commitment on the government (§10).

This paper proposes a structured, iterative framework for that question and shows how the Austral platform implements it end-to-end:

     Risk allocation (GIH baseline)
            │
            ▼
     Cost of equity (CAPM)
            │
            ▼              ◄─── feedback loop A:
     Value for Money            adjust the risk allocation
            │                   if VfM is not likely positive
            ▼              ◄─── feedback loop B:
     Financial Model             adjust VGF % until the project
     (bankability)               is bankable
            │
            ▼
     Fiscal effect
     (FCCL aggregation)

The framework is implemented in two equivalent ways:

  1. Manual mode — the practitioner walks the chain in the platform, one tool at a time, using one-click "Send to..." handoffs that forward the upstream result into the downstream tool's inputs.
  2. Automated mode — a single endpoint (POST /workflow/optimize) runs the entire loop and returns a convergence verdict, the final risk allocation, the required VGF %, and the resulting fiscal exposure.

A Workflow Optimizer dashboard (Figure 1) lets a practitioner configure the project, the concessional financing, and the gating thresholds, then watch the optimiser converge in seconds.

Figure 1 — The Workflow Optimizer dashboard.

Figure 1 — The Workflow Optimizer dashboard.

This paper provides:

The platform is at https://platform.austral-intelligence.com. Anonymous "guest mode" access is sufficient to follow the walkthrough end-to-end without creating an account.


2. The iteration logic

2.1 The sizing problem, formally

A PPP feasibility assessment is a search through a constrained design space. Once the project parameters (capex, opex, contract length, tariffs) are fixed by sector economics, the only remaining levers are the financial structure and the risk allocation. The objective is to find the smallest VGF that lets the project clear three gates simultaneously:

Sizing problem. Given a project specification (capex, opex, revenue profile, contract length) and a baseline risk allocation \(a_0\), find the design vector \((a^{*},\, v^{*},\, c^{*})\) — risk allocation, VGF as a percentage of capex, and concessional-loan structure — that solves

\[\begin{aligned} \min_{a,\,v,\,c}\quad & v \\[2pt] \text{subject to}\quad & \mathrm{ADSCR}_{\min}\!\bigl(f(a, v, c)\bigr) \;\geq\; \tau_{\mathrm{ADSCR}} && \text{(bankability)} \\ & \mathrm{IRR}_{\mathrm{eq}}\!\bigl(f(a, v, c)\bigr) \;\geq\; K_e(a) && \text{(bankability)} \\ & \mathrm{VfM}(a) \;\geq\; \tau_{\mathrm{VfM}},\quad \mathbb{P}\!\bigl(\mathrm{PSC} > \mathrm{PPP}\bigr) \;\geq\; p_{\min} && \text{(value for money)} \\ & E_j\!\bigl(f(a, v, c)\bigr) \;\leq\; C_j \quad \forall\, j \in \mathcal{J} && \text{(per-jurisdiction affordability)} \\ & 0 \;\leq\; v \;\leq\; v_{\max} && \text{(policy cap on the VGF)} \end{aligned}\]

where \(f(\cdot)\) is the financial-model engine, \(K_e(a)\) is the CAPM cost of equity (which depends on the risk premium implied by the allocation), and \(\mathcal{J}\) is the set of fiscal jurisdictions sharing the contingent liability.

Three gate families dominate every real PPP:

The design variables are:

VGF gets the spotlight because, of all the sizing knobs, it is the cleanest one for a public authority to commit to in cabinet: a single number, an upfront cash outlay, easy to compare against the fiscal envelope and against alternative public investments. The optimiser therefore treats VGF as the secondary objective (minimise) once the gates are met.

A fiscal economist's first objection is the right one to answer head-on: the smallest grant is not the smallest fiscal cost. An upfront VGF can be cheaper or dearer, in present value, than a stream of availability payments or a contingent guarantee. Minimising the VGF is a presentational default — the number cabinet actually votes on — not a claim of fiscal optimality. The practitioner who wants the smallest lifetime commitment runs the min_fiscal_npv mode (§8.6.1), which minimises the discounted sum of every commitment — grant, availability payments, guarantee payouts, terminal value — at a recorded sovereign discount rate. The framework ships both objectives precisely because they answer different decisions; it does not assert that one dominates the other.

2.2 Why iteration is unavoidable

The five stages are not independent. Each stage's output silently re-shapes the inputs of every other stage:

Stage Output that feeds back Where it lands
Risk allocation implied risk premium Cost of equity (Ke)
Cost of equity Ke (and WACC) VfM discount rate, FM equity IRR target
VfM PSC vs PPP net-present-value (NPV) difference Whether allocation needs more transfer
FM bankability min ADSCR, equity IRR, debt schedule VGF % must rise or fall
Affordability annual exposure vs ceiling Project may need to be smaller

A naive sequential walk through the chain — risk → equity → VfM → FM → affordability — almost never converges on the first pass. The practitioner sees a borderline VfM at the third stage, intuits that "a bit more risk transfer" might fix it, walks back to risk allocation, and re-runs everything downstream. Each manual cycle takes hours; ten cycles take a fortnight; a senior analyst's gut-feel choice of which knob to turn next is rarely auditable.

The iterative framework formalises that "go-back-and-tweak" instinct into two closed feedback loops the optimiser executes automatically:

                  ┌─────────────────────────────────────────────┐
                  │                                             │
                  ▼                                             │
     Risk allocation (GIH baseline)                             │
            │                                                   │
            ▼                                                   │  outer loop A
     Cost of equity (CAPM)                                      │  flip the next
            │                                                   │  transferable risk
            ▼                                                   │  toward Private
     Value for Money         ─── VfM gate fails ────────────────┘
            │                                  no more risks → INFEASIBLE
            ▼ pass
     Financial Model         ◄─── inner loop B
     (bankability)                bisect VGF % on [0, vgf_cap]
            │                     until min ADSCR ≥ target AND
            │                     equity IRR ≥ Ke AND LLCR ≥ 1
            ▼ pass
     Fiscal effect           ─── ceiling exceeded ⇒ flag breach
     (per-jurisdiction)          per affected jurisdiction
            │
            ▼
        VERDICT (with smallest VGF that satisfies every gate)

2.3 Inner loop B — VGF bisection

The inner loop is what actually sizes the VGF. Bankability is monotone in VGF: more grant money means lower outstanding debt, so ADSCR rises, equity IRR rises, LLCR rises. (Concessional loan terms shift the curves but don't change the monotonicity.) That gives us a classical monotonic bisection problem on [0%, vgf_cap%]. The search proceeds as a three-phase decision tree:

          ┌──────────────────────────────────────┐
          │ Step 1 — probe the floor: VGF = 0 %  │
          └──────────────────┬───────────────────┘
                             │
                ┌────────────┴────────────┐
            bankable                  not bankable
                │                         │
                ▼                         ▼
       return VGF = 0 %         ┌───────────────────────────┐
       (no public support       │ Step 2 — probe the ceiling │
        is needed)              │       VGF = vgf_cap        │
                                └────────────┬───────────────┘
                                             │
                                ┌────────────┴────────────┐
                            bankable                  not bankable
                                │                         │
                                ▼                         ▼
                  ┌──────────────────────────┐    return INFEASIBLE
                  │ Step 3 — bisect inside   │    (project cannot be
                  │    [lo = 0, hi = cap]    │     made bankable even
                  │  mid = ½ (lo + hi)       │     at the support cap)
                  │  bankable(mid)?          │
                  │    yes → hi ← mid        │
                  │    no  → lo ← mid        │
                  │  repeat until            │
                  │    hi − lo < tol         │
                  └────────────┬─────────────┘
                               │
                               ▼
                       return  VGF = hi
                  (the smallest VGF on the
                   bisection grid that still
                   passes every bankability
                   gate, default tol = 0.5 %)

Each step calls the financial model engine which runs the full debt waterfall, computes annual ADSCR, equity IRR, and LLCR, and returns whether all bankability gates are satisfied. With a typical project (5-tranche debt, 25-year contract, monthly waterfall), one FM run takes 0.5 to 1 second. Bisection converges in 7–10 iterations, so the inner loop completes in well under a minute even on cold starts.

A typical bisection trace on a project that needs a non-trivial subsidy (the §10 worked example happens to clear at VGF = 0 % and so does not exercise the full bisection):

INNER #1  VGF=  0.00%  ADSCR= 0.83x  IRR= 6.2%   ✗ adscr 0.83 < 1.05
INNER #2  VGF= 40.00%  ADSCR= 1.91x  IRR=27.4%   ✓ all checks pass
INNER #3  VGF= 20.00%  ADSCR= 1.31x  IRR=15.9%   ✓ pass — shrink hi
INNER #4  VGF= 10.00%  ADSCR= 1.06x  IRR=10.4%   ✓ pass — shrink hi
INNER #5  VGF=  5.00%  ADSCR= 0.95x  IRR= 8.4%   ✗ fail   — grow lo
INNER #6  VGF=  7.50%  ADSCR= 1.02x  IRR= 9.5%   ✗ fail   — grow lo
INNER #7  VGF=  8.75%  ADSCR= 1.05x  IRR=10.0%   ≈ pass   — converged
                                       ↳ smallest VGF that meets bankability

2.4 Outer loop A — risk allocation flips

If the VfM gate fails, the inner loop is irrelevant: there is no VGF that makes a value-destroying PPP attractive. The outer loop instead reaches up to the risk allocation and transfers one risk at a time from Public/Shared to Private. Each flip lifts the implied risk premium, raises Ke, and changes the PSC-vs-PPP comparison. The priority order is a transferability heuristic — most-transferable risk first — that the optimiser adopts as a practitioner convention, informed by the GIH allocation matrices but not taken from them (the GIH catalogue allocates risks Public/Private/Shared by sector; it does not rank them by transferability)2:

  1. construction cost overrun → 2. construction schedule → 3. demand → 4. O&M performance → 5. O&M maintenance → 6. design → 7. technology → 8. archaeological/environmental → 9. force majeure.

Risks that policy-and-practice never transfer (site acquisition, permitting, legal change, expropriation) are excluded from the flip list. If the optimiser exhausts every transferable risk and VfM is still failing, the verdict is infeasible as a PPP — the project should be reconsidered as a traditional public investment, divested, or redesigned.

2.5 Convergence properties and their assumptions

The discrete loop has one property that is provable and one that is a well-motivated heuristic — and audit-grade use depends on telling them apart:

  1. Monotone bisection in the inner loop does converge to the optimal VGF within tolerance, in O(log(vgf_cap / tol)) FM runs — a genuine guarantee.
  2. Greedy transferability ordering in the outer loop is optimal only under strong assumptions — that risk transfers are independent and already sorted by incremental VfM uplift. Our transferability ordering approximates that sort but does not guarantee it (and the cited GIH catalogue does not supply such a ranking), so the outer loop is a well-motivated heuristic, not a proven optimum. The Pareto and continuous modes (§8.6) exist precisely for callers who want to search the allocation space rather than trust the order.

For practitioners who want a tighter search across the full financial-structure design space (concessional rate, concessional tenor, concessional share, all simultaneously) — for example, when negotiating a multilateral loan that is itself a moving target — the platform also offers a continuous-mode optimiser (§8.6) that searches a 4-dimensional bounded box using SciPy's differential_evolution1. The continuous mode trades convergence proof for flexibility: any candidate point in the box can be evaluated, and the search is fully reproducible via a fixed RNG seed.

2.6 The Big-M penalty trick

Both loops collapse the multi-gate feasibility test into a single scalar loss so any optimisation library can drive it. The formulation is a Big-M penalty:

\[\mathcal{L}(a,\, v,\, c) \;=\; M_{a}\bigl(\tau_{\mathrm{ADSCR}} - \mathrm{ADSCR}_{\min}\bigr)^{+} + M_{i}\bigl(K_e - \mathrm{IRR}_{\mathrm{eq}}\bigr)^{+} + M_{\ell}\bigl(1 - \mathrm{LLCR}_{\min}\bigr)^{+} + v\]

where \((x)^{+} \equiv \max(0,\, x)\) and the penalty weights satisfy \(M_a,\, M_i,\, M_{\ell} \gg v_{\max}\) — in practice \(M_a = M_{\ell} = 10^{6}\) and \(M_i = 10^{5}\). This guarantees that the optimiser will always prefer a feasible (low-VGF, gates satisfied) candidate to any infeasible one, however small that infeasible candidate's VGF.

The same loss formulation drives the discrete bisection (the bankable() test is just loss == vgf for that candidate) and the continuous SciPy search (where the loss surface looks like a flat plateau of M-sized infeasible territory with a feasible "valley" the size of the bounded box; the optimiser efficiently slides into the valley and then drives vgf down within it).

2.7 Termination and audit

The combined loops terminate when one of three things happens:

Every termination — converged or infeasible — writes one row into the workspace's tamper-evident audit chain (sha256-linked ActivityLog) and is downloadable as a signed PDF with the audit row hash embedded as a QR code. A reviewer can therefore verify, without re-running the optimisation, that the report they hold is bit-for-bit the one the platform produced at the time of the decision.

Figure 2 — Iteration loop schematic.

Figure 2 — Iteration loop schematic showing the two automatic feedback paths around the five-stage feasibility chain.


3. Stage 1 — Risk allocation

3.1 Logic

Risk allocation is the foundation of every PPP. It determines:

The international convention is the Global Infrastructure Hub (GIH) risk taxonomy2, which catalogues 12–25 risks across the project lifecycle and provides default allocation guidance (Public, Private, Shared) by sector. Hellowell and Vecchi (2012)3 examine the link from the project's risk allocation to the equity-side risk premium priced into PPP equity returns; the cost-of-equity formula in §4.1 operationalises that link by attaching a basis-point increment to each risk transferred to the SPV, weighted by severity.

3.2 Austral tool: Risk (/risk)

The Risk module loads the GIH catalogue and lets the practitioner override allocations per risk. Each scenario stores:

The implied risk premium \(\pi\) is computed as

\[\pi \;=\; \pi_{\max}\cdot \frac{\sum_{r}\, s_r \cdot \alpha_r^{\mathrm{SPV}}} {\sum_{r}\, s_r \cdot 100}\]

where \(r\) indexes the risk catalogue (the GIH allocation categories), \(s_r\) is a severity weight assigned by the platform — an Austral configuration layered on the GIH categories, not a GIH-supplied value — \(\alpha_r^{\mathrm{SPV}} \in \{0, 50, 100\}\) is the share of risk \(r\) retained by the SPV (Public, Shared, Private respectively), and \(\pi_{\max} = 4.0\%\) is the platform's default ceiling on the implied risk premium (configurable per workspace, set to align with the order of magnitude of equity-side risk premia discussed in Hellowell and Vecchi (2012)3).

3.3 Gate

There is no fixed gate at this stage — risk allocation is the input the next stages consume. The optimiser treats it as the design variable it adjusts when downstream VfM fails.

Figure 3 — Risk allocation matrix.

Figure 3 — Risk allocation matrix.


4. Stage 2 — Cost of equity

4.1 Logic

The Capital Asset Pricing Model (CAPM) gives the cost of equity:

\[K_e \;=\; R_f \;+\; \beta\!\cdot\!\mathrm{ERP} \;+\; \mathrm{CRP} \;+\; \pi_{\mathrm{illiq}} \;+\; \pi\]

where:

The WACC follows:

\[\mathrm{WACC} \;=\; (1 - w_d)\,K_e \;+\; w_d\,K_d\,(1 - t)\]

with \(w_d\) the debt share, \(K_d\) the cost of debt, and \(t\) the corporate tax rate.

4.2 Austral tool: Cost of Equity (/equity/calculator)

The Equity module is a CAPM calculator backed by Damodaran's beta and country-risk-premium tables. It accepts:

It returns Ke, WACC, and the full decomposition (Rf, asset β, relevered β, Blume-adjusted β, ERP, CRP, default spread).

4.3 Gate

There is no hard gate — the practitioner sanity-checks Ke is in a plausible band (typically 10–25% in real terms for emerging markets). Out-of-band Ke usually signals an input error.

The Risk → Equity handoff is automated: the "Send to..." popover on a Risk scenario forwards the implied premium directly into the Equity calculator's additional_premium_pct field, plus seeds the country and sector hint.

Figure 4 — Risk → Equity handoff banner.

Figure 4 — Risk → Equity handoff banner.


5. Stage 3 — Value for Money

5.1 Logic

The Value-for-Money test compares the Net Present Cost (NPC) of two delivery models for the same service:

Both NPCs are discounted to the same base year. The PSC discount rate is the social discount rate (typically 5–8% real); the PPP discount rate is often the WACC of the structuring partner.

VfM = PSC_NPC - PPP_NPC (positive = PPP saves money). Expressed as % of PSC: VfM% = VfM / PSC_NPC × 100.

A Monte Carlo overlay perturbs the demand and cost assumptions to produce a distribution; the headline statistic is P(PSC > PPP) — the probability the PPP beats the comparator across the simulated paths.

5.2 Austral tool: Value for Money (/vfm)

The VfM module wraps ppp_core.engine.psc.calc_psc_and_ppp for the deterministic comparison plus ppp_core.engine.vfm_monte_carlo for the stochastic overlay. It also stores qualitative-VfM questionnaire scores (screening, incentives, NFB, full questionnaire).

5.3 Gate

The conventional gate is

\[\mathrm{VfM} \,\geq\, 5\% \quad\wedge\quad \mathbb{P}\bigl(\mathrm{PSC} > \mathrm{PPP}\bigr) \,\geq\, 0.60.\]

Both are configurable in the optimiser's gating thresholds. If either fails, feedback loop A transfers the next "transferable" risk from Public/Shared to Private (in priority order: construction overrun, schedule, demand, O&M, design, technology, archaeological, force majeure) and re-runs Stages 2–3.

The manual Equity → VfM handoff prefills the WACC as the PPP discount rate; the optimiser (§8), however, discounts the PPP-side cash flows at the cost of equity (Ke) — a deliberately conservative choice (it makes the PSC-vs-PPP test harder to pass), and the one the worked example (§10) uses.

Figure 5 — VfM screen with handoff banner.

Figure 5 — VfM screen with handoff banner.


6. Stage 4 — Financial model and bankability

6.1 Logic

The Financial Model (FM) projects the SPV's cash flows year-by-year under a specific financing structure (debt, equity, grants, concessional loans). It produces:

A project is bankable when

\[\mathrm{ADSCR}_{\min} \,\geq\, \tau_{\mathrm{ADSCR}} \quad\wedge\quad \mathrm{IRR}_{\mathrm{eq}} \,\geq\, K_e \quad\wedge\quad \text{no covenant breach.}\]

Typical lender targets: \(\tau_{\mathrm{ADSCR}}\) between \(1.20\) and \(1.30\), and equity IRR at or above the cost of equity required by the SPV's investors.

6.2 Austral tool: Financial Model (/fm)

The FM module wraps ppp_core.engine.model.run_model. The full input schema (ModelInputs) covers general project parameters, opex streams, revenue model (user-pays / availability / mixed), funding mix (5 debt tranches + equity + grants), and government payments.

The FM offers a debt-sizing solver (bisection on debt%) and a built-in health check that aggregates 10 binary tests.

6.3 Gate

The optimiser's bankability gate is

\[\mathrm{ADSCR}_{\min} \;\geq\; \tau_{\mathrm{ADSCR}} \quad\wedge\quad \mathrm{IRR}_{\mathrm{eq}} \;\geq\; K_e^{\,\text{target}}\]

where \(\tau_{\mathrm{ADSCR}}\) is the lender's covenant floor (typically \(1.20\)–\(1.30\)) and \(K_e^{\,\text{target}}\) is the cost-of-equity hurdle the SPV's investors require — by default the CAPM-derived Ke from Stage 2, but caller-overridable in the optimiser's gating thresholds.

If the gate fails, feedback loop B bisects the VGF % of capex on [0%, vgf_cap%] until bankability passes. VGF is injected as funding.government_contribution = vgf_pct × total_investment.

The user supplies the concessional loan (share, rate, tenor, grace) as a fixed input — the optimiser doesn't tune it, since it is typically a policy choice or a multilateral commitment.

Figure 6 — Financial Model bankability output.

Figure 6 — Financial Model bankability output.


7. Stage 5 — Fiscal affordability

7.1 Logic

A bankable project may still be unaffordable. The fiscal affordability test compares the government's annual exposure across the contract life against the fiscal ceiling:

For each year, the exposure / ceiling ratio gives a usage %; any year above 100 % marks a ceiling breach.

The IMF/World Bank PFRAM 2.05 is the de facto reference tool for this aggregation — an analytical tool aligned with IPSAS 32 / GFSM 2014 rather than an accounting standard itself; Austral's FARO module (its fiscal-affordability and FCCL-aggregation tool) follows its approach.

7.2 Austral tool: Fiscal Affordability (/faro/affordability)

FARO's affordability endpoint takes the workspace's PPP project portfolio, projects GDP forward at the macro growth rate, and computes direct + contingent exposure year-by-year. It returns:

An FM-to-affordability adapter (via the FM-to-affordability adapter) auto-translates FM-shape projects (which use a different schema than PFRAM) into the PFRAM-shape series the affordability calc expects, so a bankable FM project can flow into the FCCL check with no manual re-import.

7.3 Gate

\[E_j(t) \,\leq\, C_j \quad \forall\, t \in [t_0,\, t_0 + T],\ \forall\, j \in \mathcal{J}\]

i.e. for every year \(t\) over the contract horizon \(T\) and every jurisdiction \(j\), exposure \(E_j(t)\) stays at or below the jurisdiction's ceiling \(C_j\).

If the gate fails, the project is infeasible at the planned scale. Mitigations: defer construction start, reduce capex, shift to a user-pays revenue model, or cap government commitments.

Figure 7 — FARO affordability after the FM-to-affordability adapter runs.

Figure 7 — FARO affordability after the FM-to-affordability adapter runs. The exposure reads ~0.00% of GDP because the seeded demo is a user-pays toll concession at VGF = 0%: it places no direct fiscal commitment on the government, so a near-zero FCCL series is the correct result, not an empty one. The adapter is still exercised — it carries the FM cash flows into the FCCL check, which here returns ~0.


8. The optimiser architecture

8.1 Two solution modes for one sizing problem

The platform ships two algorithms that solve the same sizing problem from §2.1 with different trade-offs:

Both modes use the same financial-model engine, the same VfM engine and Monte Carlo, the same per-jurisdiction FCCL aggregation, and the same audit chain. A discrete-mode verdict and a continuous-mode verdict on the same project are therefore directly comparable: any difference in the recommended VGF is attributable purely to the difference in search strategy, not to a change in the underlying calculation.

The technical specifications of both modes — request bodies, response shapes, algorithm configuration, default bounds — live in Appendix C so this section can stay conceptual.

8.2 Feedback loop A — adjusting the risk allocation

When the VfM gate fails, the discrete optimiser walks down a fixed priority list of transferable risks and flips them, one at a time, from Public or Shared toward Private. The list is the international convention for "this risk can move":

  1. Construction cost overrun
  2. Construction schedule
  3. Demand
  4. O&M performance
  5. O&M maintenance
  6. Design
  7. Technology
  8. Archaeological / environmental
  9. Force majeure

Each flip lifts the implied risk premium, raises the cost of equity, and changes the PSC-vs-PPP comparison. Risks that policy and practice never transfer (site acquisition, permitting, legal change, expropriation) are excluded from the list and remain Public regardless of the VfM verdict. If the optimiser exhausts the transferable list without VfM passing, the verdict is infeasible as a PPP — the project should be reconsidered as a traditional public investment, divested, or redesigned.

8.3 Feedback loop B — sizing the Viability Gap Funding

VGF is searched on [0 %, vgf_cap %] with a default cap of 40 % of capex. The bisection terminates at a 0.5 % tolerance — i.e. the recommended VGF is reported within half a percentage point of capex of the smallest value the bankability gate accepts. The first probe at 0 % checks whether the project is already bankable without any public support; the second probe at the cap checks whether even the maximum support is enough. If both probes fail in the same direction, the verdict is infeasible rather than a meaningless midpoint number.

8.4 Module-to-module bridges

The five-stage chain runs on five thin "bridges" — one per hop — that translate the output shape of one Austral module into the input shape of the next. The bridges are conceptually:

# Hop What the bridge does
A Risk → Equity Derive an implied risk premium from the allocation matrix and feed it as the SPV-specific premium into CAPM.
B Equity → VfM Discount the PPP-side cash flows at the cost of equity (Ke) for the PSC-vs-PPP comparison (a conservative choice — the manual prefill forwards the WACC, but the optimiser uses Ke).
C Equity → FM Forward the cost of equity as the equity-IRR target for the bankability gate.
D VfM → FM Forward the underlying ModelInputs so the bankable model is the same project the VfM Monte Carlo accepted.
E FM → Affordability Aggregate FM cash flows into the PFRAM shape and run the fiscal-ceiling calc per jurisdiction.

Each bridge is a thin adapter (~30 lines of code) that the optimiser calls in sequence; the practitioner can also call them one at a time from the UI, via the "Send to…" handoff buttons, when working in manual mode.

8.5 The optimiser endpoints — one verdict per call

Both modes are exposed as a single HTTP POST that returns a verdict payload — a one-line summary, the smallest converged VGF, the per-jurisdiction fiscal-exposure series, and a per-iteration trace that explains how the search got there. The discrete endpoint also has a Server-Sent-Events sibling that streams each iteration step as it lands, so the UI shows live convergence rather than a spinner. A typical synchronous wall time is 1 to 60 seconds for the discrete mode and 30 to 60 seconds for the continuous mode (40 to 60 financial-model evaluations at roughly half a second each).

The full request and response schemas, including all gating thresholds, jurisdiction inputs, and continuous-mode bounds, are documented in Appendix C.

8.6 Continuous mode — when to reach for it

The continuous mode searches a four-dimensional bounded box over VGF percentage, concessional share, concessional rate, and concessional tenor, holding the risk allocation fixed at the user-supplied baseline. Inside the box, the same Big-M loss formulation from §2.6 collapses every gate violation into a single scalar that any black-box optimiser can drive. The platform uses SciPy's differential-evolution algorithm because it is already shipped with the engine, handles bounded continuous problems natively, and — crucially for an audit-grade tool — is fully reproducible from a single integer seed. Re-running the same inputs with the same seed produces a bit-for-bit identical trace, which is what makes the continuous-mode verdict signable in the same way the discrete-mode verdict is.

The trade-off is straightforward: the continuous mode buys flexibility (and richer trade-off information across the financing-structure design space) at the cost of a longer wall time and a search that converges to the best candidate seen rather than to a provably optimal point. For decisions that need to be defended on convergence guarantees alone, the discrete mode is preferred; for decisions where the financing package is itself a moving target, the continuous mode is the better fit.

8.6.1 Objective family

The continuous mode supports three objectives, all sharing the same gates and the same Big-M penalty for infeasible candidates:

Objective Secondary term to minimise When to use
min_vgf (default) \(v\) — the VGF as % of capex Cabinet decision: "what's the smallest upfront grant we can commit to?"
min_fiscal_npv \(\displaystyle \frac{1}{\mathrm{capex}}\!\left[v\cdot\mathrm{capex} + \sum_{t=1}^{T}\frac{\mathrm{AP}_t + \mathrm{Grants}_t}{(1+r_g)^t} + \frac{\mathrm{Terminal}}{(1+r_g)^T}\right]\) Treasury decision: "what financing structure minimises the present value of every commitment over the contract life?" The required sovereign_discount_rate_pct is recorded in the audit row so the answer can be replayed with a different rate.
max_irr_within_envelope \(-\mathrm{IRR}_{\mathrm{eq}}\), with an additional Big-M penalty if \(v > V_{\mathrm{cap}}\) Procurement decision: "we have a hard fiscal envelope of \(V_{\mathrm{cap}}\) — get us the most attractive bid the market will respond to." Requires vgf_envelope_pct.

The three answers diverge in cases where each one matters most. min_vgf and min_fiscal_npv agree when the project is mostly user-pays (no AP / grants stream), but split decisively for availability-based PPPs at long tenors and high sovereign discount rates. max_irr_within_envelope typically pushes VGF up to the cap to maximise leverage and equity returns — which is exactly what a competitive procurement wants to see.

All three objectives use the same FM engine, the same VfM Monte Carlo, the same per-jurisdiction FCCL aggregation, and the same audit chain. A run from any objective is directly comparable to a run from any other on the same project.

8.6.2 Multi-objective Pareto front

Collapsing every gate violation and every sizing knob into a single scalar is the right thing to do when the policymaker has already made the priority call ("we want the smallest grant"). When the policymaker hasn't — when it's still genuinely a trade-off between upfront grant, equity returns, and lifetime fiscal cost — the right output isn't one answer, it's a trade-off curve.

The platform exposes POST /workflow/optimize/pareto, which runs NSGA-II6 (a standard multi-objective evolutionary algorithm, implemented here in pure numpy with no extra dependency) over the same four design variables as the continuous mode, with three competing objectives:

\[\min_{a,\,v,\,c}\; \bigl(\, v,\; -\mathrm{IRR}_{\mathrm{eq}},\; \mathrm{NPV}_{\mathrm{fiscal}}(v,c;r_g)\,\bigr) \quad\text{s.t. all gates pass.}\]

The Big-M penalty from §2.6 is added to each objective component so infeasible points dominate themselves out of the front naturally. The endpoint returns the non-dominated set of candidates plus each candidate's full design vector and KPIs. The frontend renders three pairwise 2-D scatters (VGF × IRR, VGF × NPV, IRR × NPV) with the front in purple over the dominated cloud in grey, plus a sortable click-to-select table of front candidates. Picking a point on the curve (cheap-upfront-but-low-IRR vs. competitive-bid-but- expensive) replaces picking the answer.

Reproducible via fixed seed; audit chain compatible.

8.6.3 Robust optimisation under stress

Bankability at the central case isn't bankability under the scenarios that actually break PPPs. The platform therefore exposes POST /workflow/optimize/robust, which wraps the discrete-mode optimiser in an outer stress loop and reports the smallest VGF that keeps the project bankable across every shock in a user-selected catalogue:

\[\min_{a,\,v,\,c}\; v \quad\text{s.t.}\quad \text{gates pass}\bigl(f(a, v, c, s)\bigr)\quad \forall\, s \in \mathcal{S}.\]

The MVP ships with a built-in catalogue of five shocks:

For each candidate VGF, the optimiser re-runs the FM under every shock and only counts it as feasible if every shock clears the gate. The verdict identifies the binding shock (the one that forces the highest required VGF) and per-shock binding status, so the practitioner sees not just "you need 18% VGF to be robust" but "the +200 bps rate shock is what's binding — if you can hedge that out, the required VGF drops to 11%". This is the natural ask from rating agencies, fiscal-risk officers, and lender independent monitors.

The discrete mode's outer loop walks a fixed priority list of transferable risks one flip at a time — a defensible heuristic but not an exhaustive search. For projects with strongly correlated risks (e.g. construction overrun ↔ schedule overrun) the greedy policy can miss combinations that a continuous search finds. The Pareto endpoint accepts a flag search_risk_allocation: true that extends the design vector from 4 to 13 dimensions: each of the nine transferable GIH risks gets a continuous αr ∈ [0, 1] representing the SPV's retained share of that risk, smooth-thresholded to Public/Shared/Private at 0.25/0.75 for the verdict. The implied risk premium and per-candidate Ke are recomputed from the alpha vector inside the search loop. The four risks policy never transfers (site acquisition, permitting, legal change, expropriation) are not in the search vector — they are held at their baseline (Public) throughout, so the continuous search never proposes a transfer the discrete loop's exclusion list also forbids.

VfM Monte Carlo isn't re-run per candidate — at 300 simulations per VfM call, that would multiply search cost by ~300×. The MVP keeps VfM as a one-shot baseline computation and lets the per-candidate Ke do the discriminating work; a more rigorous "VfM-per-candidate" mode is on the roadmap.

8.6.5 Bayesian optimisation as an alternative algorithm

The continuous-mode endpoint accepts an algorithm flag with two options:

If scikit-optimize isn't installed on the host, the optimiser falls back to differential-evolution with a recorded algorithm_used field in the audit row. No error, no behaviour change for existing callers; the choice is a soft preference, not a hard requirement.

8.7 Per-jurisdiction and multi-currency awareness

Both modes accept a list of jurisdictions — federal, state, municipal — each carrying a configurable share of the project's contingent liability and judged against its own fiscal ceiling expressed as a percentage of its own GDP. The fiscal-effect block reports per-entity peak exposure, headroom, and breach year (if any). The verdict surfaces a one-line warning when any jurisdiction breaches its ceiling, even if the headline (federal- only) ceiling is comfortably within bounds — a state-level contingent liability can sink an otherwise-affordable project at the level of government that actually carries it.

The optimiser also accepts a reporting-currency translation: the financial-model engine continues to run in the project's financing currency, but the headline fiscal-effect series is translated into the reporting currency for direct comparison against the government's own ceilings. The translation supports two modes:

When a path is supplied, the response carries the full fx_path back so the frontend can chart it alongside the fiscal-effect series. The headline fx_rate field reports the path average for at-a-glance display.

8.8 Async job queue

Multi-objective Pareto runs and per-shock robust loops routinely take longer than the 60-second comfort window for a synchronous HTTP request. The platform exposes a generic asynchronous-job interface so the frontend doesn't block:

The MVP runs the worker in-process via FastAPI BackgroundTasks rather than dispatching through a Celery broker. Same UX as a production Celery setup (immediate job_id response, polling for status, cancellation, audit-row-on-completion); when the deployment needs horizontal worker scaling, the dispatcher swaps to Celery in a single method — the row schema, the polling endpoint, and the frontend stay identical.

8.9 Audit chain and signed PDFs

Every optimiser run — converged or infeasible, discrete or continuous — writes one row into the workspace's tamper-evident audit chain. Each row is sha256-linked to the previous one, so inserting, deleting, or rewriting any row in the past breaks every hash downstream and is detectable by inspection. The recorded payload contains the full input body, a canonical hash of the inputs, a canonical hash of the trace, the headline KPIs, and the per-jurisdiction breach status.

Each row can be downloaded as a signed PDF that re-renders the verdict and embeds the row hash both as printed text and as a QR code in the page header. A Supreme Audit Institution or lender independent monitor can scan the QR code, query the platform's audit endpoint, and confirm that the report they hold is bit-for-bit the canonical one — without trusting the platform itself. The PDF is generated by deterministically replaying the optimisation against the stored inputs, and an X-Replay-Match HTTP header indicates whether the replay produced the same trace hash as the chain. Any drift between replay and chain is a tamper signal that a reviewer can act on.


9. Walkthrough — reproduce in 10 minutes

This section is the practical core of the paper. A reader with no prior Austral exposure can reproduce the entire feasibility loop end- to-end by following these eight steps.

Step 1. Open the platform

Navigate to https://platform.austral-intelligence.com. The platform automatically creates an anonymous "guest" workspace on first visit; no signup is required.

Figure 8 — Workspace dashboard with the Workflow Optimizer button.

Figure 8 — Workspace dashboard with the Workflow Optimizer button.

Step 2. Seed the demo data

In the left sidebar, open Financial Model. Click Demo Reset → Load demo pack. Five demo projects appear (Accra-Tema Motorway, Atacama Solar PV, Bogota Airport T3, Lagos Water Treatment, Regional Hospital Nairobi).

Repeat for Risk Platform so a baseline GIH allocation matrix is available.

Figure 9 — Seeded FM portfolio.

Figure 9 — Seeded FM portfolio.

Step 3. Open the Workflow Optimizer

Click the green Workflow Optimizer button in the workspace dashboard header (or navigate to /workspaces/<wid>/workflow/optimize).

Figure 10 — Workflow Optimizer landing page.

Figure 10 — Workflow Optimizer landing page.

Step 4. Configure the inputs

Card Field Default What it means
Project source dropdown Accra-Tema Motorway Pick any FM scenario as the base project
Concessional loan Share % of debt 25 What fraction of debt is concessional
Concessional loan Rate % 3.5 Concessional annual interest rate
Concessional loan Tenor (yrs) 25 Repayment period
Concessional loan Grace (yrs) 4 Grace period before repayment starts
CAPM Country Ghana Damodaran country lookup for CRP (matches the Accra–Tema demo)
CAPM Sector mix Engineering/Construction (100 %) Drives the unlevered asset beta
CAPM Bond yield % 6.5 10-year sovereign yield (Rf)
CAPM D/E % 250 Project debt-to-equity ratio
CAPM Tax % 25 Corporate tax rate
CAPM Cost of debt % 8 Senior commercial debt rate
Gating VfM % min 5 Minimum VfM% for "positive" verdict
Gating P(PSC>PPP) min 0.6 Minimum confidence from MC
Gating Min ADSCR target 1.05 Lender's minimum coverage
Gating Equity IRR min % 18 Required equity return
Gating Max outer iter 8 Cap on risk-allocation flips
Gating Max inner iter 12 Cap on VGF bisection steps
Gating VGF cap % 40 Maximum VGF as % of capex

You can click Load demo defaults to populate all fields with known-converging values.

Step 5. Click Run optimizer

The button shows a spinner; backend computation typically takes 1–60 seconds.

Figure 11 — Optimizer running.

Figure 11 — Optimizer running.

Step 6. Read the verdict

The result panel renders three areas:

  1. Verdict card (green = converged, amber = infeasible) with the one-line verdict and 8 KPI tiles: implied risk premium, Ke, WACC, min ADSCR, equity IRR, VfM%, P(PSC>PPP), and required VGF %.
  2. Iteration trace — each step revealed at 250ms intervals, showing what the optimiser was thinking at each point.
  3. Fiscal effect chart — annual availability payments + grants stacked, with debt outstanding as a line on the secondary axis.

Figure 12 — Converged verdict with KPI tiles.

Figure 12 — Converged verdict with KPI tiles.

Step 7. Walk the iteration trace

Each row in the trace is one decision point:

Figure 13 — Iteration trace expanded.

Figure 13 — Iteration trace expanded.

Step 8. Inspect the fiscal effect

Below the trace, a stacked bar chart shows the annual fiscal commitments year-by-year, with debt outstanding overlaid. The three summary tiles report max contingent (peak), total direct commitments (nominal sum), and terminal handback value.

Figure 14 — Fiscal effect chart + summary tiles.

Figure 14 — Fiscal effect chart + summary tiles.

You now have a complete feasibility report on this project that can be replicated, parameterised, and re-run.


10. Worked example

Project: Accra-Tema Motorway Extension (Ghana, 30-year user-pays toll concession, $620M total investment — the platform's seeded demo project)

Inputs: - 25% concessional debt at 3.5% (25-year tenor, 4-year grace) - CAPM: Ghana, 6.5% Rf, Engineering/Construction sector, 250% D/E, 25% tax, 8% cost of commercial debt - Gating: VfM% ≥ 5%, P(PSC>PPP) ≥ 0.6, Min ADSCR ≥ 1.05, Equity IRR ≥ 18%

Demo-data run (1.0s wall time):

status: converged

Iteration trace
  LOOP A #1  Public/Private/Shared = 3/4/6
             implied_premium = 2.15%   Ke = 27.37%   WACC = 12.11%
             VfM% = 132.86%            P(PSC>PPP) = 1.00
             gate: VfM is positive  (no risk flip needed)

  LOOP B #1  VGF = 0.00%
             min ADSCR = 1.052x   Equity IRR = 22.08%
             gate: PASS

Verdict
  Bankable with VGF = 0% of capex after 1 outer and 1 inner iterations.

Final fiscal exposure (selected years). This is a user-pays toll concession, so the government makes no availability payments and, at VGF = 0%, no grant — its direct fiscal commitment is effectively zero. The debt outstanding is the SPV's, shown for context, not a government liability:

Year Govt availability payments ($M) Govt grant / VGF ($M) SPV debt outstanding ($M)
2026 0 0 465
2030 0 0 410
2040 0 0 184
2050 0 0 0

Reading the result: the project is bankable WITHOUT public support (VGF = 0%) at the relaxed 1.05x ADSCR target, on the baseline risk allocation (3 Public / 4 Private / 6 Shared, no flips needed). Cost of equity is 27.4% (Ghana's high sovereign-plus-emerging-market risk premium, relevered to 250% D/E); WACC is 12.1% once the 25% concessional tranche and senior debt are blended in. One honest caveat: the equity-IRR gate in this run is the sponsor's stated 18% hurdle — below the 27.4% CAPM cost of equity — so VGF = 0% means the structure clears that 18% hurdle and the 1.05x ADSCR floor, with equity earning 22.08%; holding equity to its full CAPM cost of equity would require a positive VGF. Surfacing exactly that distinction is the point of the gate. Because this is a user-pays toll concession carrying no subsidy, the government's direct fiscal commitment is effectively zero — the fiscal-effect view (Figure 14) correctly shows ~$0 of direct commitments and only the SPV's declining debt-outstanding profile, which is exactly what a user-pays structure should produce. (The reported VfM of 132.86% is a demo-data artefact: the seeded public-sector-comparator inputs are deliberately unfavourable to public delivery, so read it as "the VfM gate passes comfortably", not as a realistic 132% margin.)

Counterfactual: raising the bankability target to 1.30x makes the project infeasible even at 40% VGF — the demo project's revenue profile simply can't deliver lender-grade coverage. This is exactly the kind of finding the framework surfaces: a project that LOOKS attractive on VfM may not be bankable; a project that's bankable may not be affordable; the iteration loop systematically searches for the structure that satisfies all three.


11. Limitations and roadmap

11.1 What this version assumes

11.2 What this version ships

Eleven distinct features power the framework. Each writes into the same tamper-evident audit chain, so any one of them can be reviewed by an SAI or lender independent monitor with the same QR-coded signed PDF.

# Feature Where Notes
1 Discrete optimiser (greedy + bisection) §8.1–§8.3 Original audit-grade default.
2 Continuous optimiser (SciPy DE) §8.6 4-D search over VGF + concessional structure.
3 Live SSE iteration stepper + step commentary §8.5 Real-time progress; one-line commentary per step, generated from the step's own numbers.
4 Tamper-evident audit chain + signed PDF §8.9 sha256 chain, QR-coded row hash, X-Replay-Match header.
5 Per-jurisdiction + multi-currency awareness §8.7 Federal/state split, per-jurisdiction breach status, scalar FX or per-year fx_path.
6 Objective family §8.6.1 min_vgf, min_fiscal_npv, max_irr_within_envelope.
7 Multi-objective Pareto front (NSGA-II) §8.6.2 Non-dominated set over VGF × IRR × fiscal NPV.
8 Robust optimisation under stress §8.6.3 5-shock catalogue; reports binding shock + worst-case VGF.
9 Continuous risk-allocation search §8.6.4 13-D NSGA-II with per-candidate Ke from α-derived implied premium.
10 Bayesian optimisation alternative §8.6.5 algorithm: "bayesian" flag with graceful DE fallback.
11 Async job queue §8.8 Generic /jobs dispatcher with status polling.

11.3 Roadmap items still open

The follow-up list is deliberately short. None of these is a blocker for any of the eleven shipped features; each adds depth in a specific direction.

11.4 Where to give feedback

Appendices

The appendices below are reference material for practitioners and implementers. They restate the API contracts, the algorithm configuration, the cross-module bridges, and the glossary in the form most useful for adapting the framework to a different platform or a different country setup. None of the appendices is required reading for the main argument of the paper.


Appendix A — Reproducible API call

The same optimisation can be run via API (no UI required) for embedding in batch pipelines or external tools:

TOKEN=$(curl -sS -X POST https://platform.austral-intelligence.com/api/v1/auth/guest \
  -H "Content-Type: application/json" -d '{}' | jq -r .access_token)
WID=$(curl -sS -H "Authorization: Bearer $TOKEN" \
  https://platform.austral-intelligence.com/api/v1/workspaces | jq -r '.[0].id')

# (Optional) seed demo data
for t in fm risk; do
  curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
    "https://platform.austral-intelligence.com/api/v1/workspaces/$WID/$t/demo/seed"
done

# Pull the demo project's model_inputs
PID=$(curl -sS -H "Authorization: Bearer $TOKEN" \
  "https://platform.austral-intelligence.com/api/v1/workspaces/$WID/projects?source=fm" \
  | jq -r '.[0].id')
MI=$(curl -sS -H "Authorization: Bearer $TOKEN" \
  "https://platform.austral-intelligence.com/api/v1/workspaces/$WID/projects/$PID" \
  | jq '.project_data.model_inputs')

# Run the optimiser
PAYLOAD=$(jq -n --argjson mi "$MI" '{
  project_inputs: $mi,
  risk_baseline: {},
  concessional_loan: {share_pct: 25, rate_pct: 3.5, tenor_years: 25, grace_years: 4},
  capm: {country: "Ghana", bond_yield_pct: 6.5,
         sectors: [{sector: "Engineering/Construction", weight_pct: 100}],
         de_ratio_pct: 250, tax_rate_pct: 25,
         beta_formula: "hamada", apply_blume: true,
         illiquidity_pct: 1.0, cost_of_debt_pct: 8.0},
  gating: {vfm_pct_min: 5, p_psc_gt_ppp_min: 0.6,
           min_adscr_target: 1.05, equity_irr_min_pct: 18.0}
}')

curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "$PAYLOAD" \
  "https://platform.austral-intelligence.com/api/v1/workspaces/$WID/workflow/optimize" \
  | jq '.summary'

Expected output (using the seeded demo): a converged verdict with the same KPI block the UI displays.


Appendix B — Module-to-module adapter reference

Endpoint Method Purpose
/handoff/targets/{source_module} GET List valid handoff targets for a source module
/handoff/equity/from-risk/{rid} GET Risk → Equity prefill (additional_premium_pct + country + sector hint)
/handoff/vfm/from-risk/{rid} GET Risk → VfM prefill (PSC/PPP risk allocation hint)
/handoff/vfm/from-equity/{eid} GET Equity → VfM prefill (WACC as ppp_discount_rate)
/handoff/fm/from-equity/{eid} GET Equity → FM prefill (Ke as target equity IRR)
/handoff/fm/from-vfm/{vid} GET VfM → FM prefill (full model_inputs forward)
/faro/affordability/from-fm POST FM-to-affordability adapter — aggregates FM cash flows for the fiscal-ceiling check
/workflow/optimize POST Run the entire chain with auto-iteration

Appendix C — Algorithm specification and API contracts

The main text of §8 keeps the optimiser conceptual. This appendix restates the algorithm configuration and the HTTP contracts in implementation terms, suitable for reproducing the work on a different platform or for embedding the optimiser in a batch pipeline.

C.1 Discrete-mode pseudocode

DISCRETE MODE — outer loop A (risk allocation, max 10 iterations):
  premium = implied_premium(allocation)
  Ke      = CAPM(country, sectors, premium, …)
  vfm     = run_psc_ppp_and_mc(project, ke)
  if VfM gate passes: break
  else: flip the next transferable risk to Private → repeat

DISCRETE MODE — inner loop B (VGF, max 15 iterations bisected on [0%, vgf_cap%]):
  inputs = inject(project, concessional_loan, vgf_mid)
  fm     = run_fm(inputs)
  if bankable(fm, target_adscr, target_ke): vgf_hi = mid
  else: vgf_lo = mid
  break when |vgf_hi - vgf_lo| < tol  (default 0.5%)

FINAL STAGE (both modes):
  fccl = aggregate_per_jurisdiction(fm, jurisdictions, fx_rate)
  return fiscal_effect(fccl), per-jurisdiction breach status

C.2 Continuous-mode pseudocode

CONTINUOUS MODE — single 4-D search:
  bounds   = [(vgf_min, vgf_max), (share_min, share_max),
              (rate_min, rate_max), (tenor_min, tenor_max)]
  loss(x)  = Big-M(adscr_violation, irr_violation, llcr_violation) + x[vgf]
  result   = differential_evolution(loss, bounds=bounds, seed=seed,
                                    maxiter=maxiter, popsize=popsize,
                                    init='sobol', polish=False)
  return  best_so_far(x), full per-evaluation trace

Configuration choices:

C.3 Discrete-mode API contract

POST /workspaces/{wid}/workflow/optimize with body:

{
  "project_inputs": { ... full ModelInputs },
  "risk_baseline": { "risk_id": "Public" | "Private" | "Shared", ... },
  "concessional_loan": { "share_pct": 25, "rate_pct": 3.5, "tenor_years": 25, "grace_years": 4 },
  "capm": { "country": "Ghana", "bond_yield_pct": 6.5, "sectors": [...], "de_ratio_pct": 250, "tax_rate_pct": 25, "cost_of_debt_pct": 8.0 },
  "gating": { "vfm_pct_min": 5, "p_psc_gt_ppp_min": 0.6, "min_adscr_target": 1.05, "max_outer_iter": 8, "max_inner_iter": 12, "vgf_cap_pct": 40 },
  "jurisdictions": [{ "name": "federal", "share_pct": 100, "fiscal_ceiling_pct_gdp": 2.0, "gdp_nominal_m": 0 }],
  "currency": { "reporting_currency": "USD", "financing_currency": "USD", "fx_rate": 1.0 }
}

returns:

{
  "status": "converged" | "infeasible" | "max_outer_iter",
  "trace": [ ... per-iteration snapshot ... ],
  "summary": {
    "verdict": "Bankable with VGF=12.5% of CapEx after 2 outer and 7 inner iterations.",
    "outer_iterations": 2,
    "inner_iterations": 7,
    "final_allocation": { ... },
    "final_ke_pct": 16.2,
    "final_vfm_pct": 8.4,
    "final_min_adscr": 1.31,
    "final_vgf_pct": 12.5,
    "fiscal_effect": { "max_contingent_peak": ..., "annual_availability_payments": [...], "by_jurisdiction": [...] },
    "any_jurisdiction_breach": false,
    "audit_id": "...",
    "audit_row_hash": "...",
    "elapsed_sec": 4.1
  }
}

A live-streamed sibling endpoint at POST /workspaces/{wid}/workflow/optimize/sse accepts the same body and emits each iteration step as a Server-Sent Event so the UI can render convergence progress in real time.

C.4 Continuous-mode API contract

POST /workspaces/{wid}/workflow/optimize/continuous with body:

{
  "project_inputs": { ... full ModelInputs },
  "risk_baseline": { ... fixed allocation, the search does not flip risks },
  "capm": { ... },
  "gating": { ... same gates as discrete mode },
  "bounds": {
    "vgf_pct_min": 0,    "vgf_pct_max": 30,
    "conc_share_pct_min": 10, "conc_share_pct_max": 50,
    "conc_rate_pct_min":  1.5, "conc_rate_pct_max": 6.0,
    "conc_tenor_years_min": 18, "conc_tenor_years_max": 30
  },
  "concessional_grace_years": 4,
  "jurisdictions": [...],
  "currency": {...},
  "seed": 42,
  "max_evals": 60,
  "objective": "min_vgf"
}

A converged response includes the winning candidate's design vector (best_x), the full per-evaluation trace (with each evaluation's x, loss, kpis, feasible flag, and best_so_far_loss), and the same summary block as the discrete mode so the same downstream tools (verdict card, fiscal-effect chart, audit log, signed PDF) work unchanged.

C.5 Pareto API contract

POST /workspaces/{wid}/workflow/optimize/pareto with body:

{
  "project_inputs": { ... },
  "risk_baseline": { ... },
  "capm": { ... },
  "gating": { ... },
  "jurisdictions": [...],
  "currency": {...},
  "concessional_grace_years": 4,
  "bounds": { ... same shape as continuous mode ... },
  "sovereign_discount_rate_pct": 6.0,
  "seed": 42,
  "population_size": 40,
  "n_generations": 20,
  "search_risk_allocation": false
}

Returns front (list of non-dominated candidates with full design vector + KPIs + per-jurisdiction fiscal effect + (when search_risk_allocation=true) per-candidate allocation_summary and ke_pct), trace (every evaluation), evaluations, n_generations, population_size, seed, n_dim (4 or 13). The synchronous endpoint is soft-capped at population_size × n_generations ≤ 250; larger searches must use the async job-queue (§C.7).

C.6 Robust API contract

POST /workspaces/{wid}/workflow/optimize/robust with body:

{
  "project_inputs": { ... },
  "risk_baseline": { ... },
  "concessional_loan": { ... },
  "capm": { ... },
  "gating": { ... },
  "jurisdictions": [...],
  "currency": {...},
  "stress_set": [
    "rate_plus_200bps",
    "traffic_minus_10pct",
    "fx_depreciation_15pct",
    "opex_overrun_5pct",
    "capex_overrun_10pct"
  ],
  "vgf_pct_min": 0.0,
  "vgf_pct_max": 40.0,
  "vgf_tol_pct": 0.5,
  "max_inner_iter": 15
}

Returns baseline_vgf_pct, robust_vgf_pct (worst across all shocks), binding_shock, vgf_uplift_pct, per_shock (array of per-shock smallest-bankable-VGF + binding status), n_shocks_evaluated, n_infeasible, plus the standard fiscal_effect block on the binding (worst-case) candidate. A companion GET /workflow/optimize/robust/catalogue lists the built-in shock catalogue.

C.7 Async job queue

Endpoint Purpose
POST /workflow/optimize/jobs Submit a long-running job. Body is {kind, body} where kind ∈ {"continuous", "pareto"} and body is the kind-specific payload. Returns immediately with {id, status: "queued", ...}.
GET /workflow/optimize/jobs List the workspace's recent jobs, newest first.
GET /workflow/optimize/jobs/{id} Poll one job. Status transitions: queued → running → completed | failed | cancelled. On completion, result_audit_id points at the audit row that records the search trace + verdict.
POST /workflow/optimize/jobs/{id}/cancel Best-effort cancellation. The worker checks the cancelled set between phases.

C.8 Audit endpoints

Endpoint Purpose
GET /workflow/optimize/runs List every optimiser run for the workspace, newest first, with KPI summary and audit row hashes.
GET /workflow/optimize/runs/{audit_id} Return one run's full audit detail (the row's recorded payload, including the stored inputs and trace hashes).
GET /workflow/optimize/runs/{audit_id}/report.pdf Re-render the run as a signed A4 PDF with the row hash printed and embedded as a QR code in the page header. The response also carries an X-Replay-Match header indicating whether the deterministic replay produced the same trace hash as the chain.

Appendix D — Glossary

Term Meaning
ADSCR Annual Debt Service Coverage Ratio — CFADS / annual debt service
FM-to-affordability adapter The single hop from a bankable FM project to its fiscal-ceiling check
CAPM Capital Asset Pricing Model — Ke = Rf + β·ERP + premiums
CFADS Cash Flow Available for Debt Service
CRP Country Risk Premium (from Damodaran's default-spread × volatility multiplier)
ERP Equity Risk Premium (mature market)
FCCL Fiscal Commitments and Contingent Liabilities
GIH Global Infrastructure Hub — widely-used reference risk-allocation matrices (Public/Private/Shared by sector)
Handoff Cross-module input transfer in Austral; triggered by "Send to..." button
Ke Cost of equity (real or nominal, project-specific)
LLCR Loan Life Coverage Ratio — NPV(CFADS over loan life) / debt outstanding
MRG Minimum Revenue Guarantee
PFRAM IMF/WB PPP Fiscal Risk Assessment Model — methodology for FCCL aggregation
PLCR Project Life Coverage Ratio — NPV(CFADS over project life) / debt outstanding
PPP Public–Private Partnership
PSC Public Sector Comparator
SPV Special Purpose Vehicle (the project company)
VfM Value for Money
VGF Viability Gap Funding (upfront grant from government, % of capex)
WACC Weighted Average Cost of Capital


  1. Storn, R., & Price, K. (1997). "Differential Evolution — A Simple and Efficient Heuristic for Global Optimization over Continuous Spaces." Journal of Global Optimization, 11 (4), 341–359. The algorithm Austral's continuous mode invokes via SciPy's scipy.optimize.differential_evolution. https://doi.org/10.1023/A:1008202821328. ↩

  2. Global Infrastructure Hub. Allocating Risks in Public-Private Partnership Contracts (PPP Risk Allocation Tool), World Bank Group / G20 Global Infrastructure Hub. The per-sector default Public/Private/Shared allocations used by the discrete-mode outer loop are taken from this catalogue's matrices; the transferability ordering in which the outer loop flips risks is Austral's own practitioner heuristic and is not part of the GIH catalogue (which does not rank risks by transferability). https://www.gihub.org/resources/publications/allocating-risks-in-public-private-partnership-contracts/. ↩↩

  3. Hellowell, M., & Vecchi, V. (2012). "What return for risk? The price of equity capital in public-private partnerships." Paper presented at the International Research Society for Public Management (IRSPM) Annual Conference, 2012. Used as the conceptual reference for linking project risk allocation to the equity-side risk premium that the SPV prices into PPP equity returns. https://www.research.ed.ac.uk/en/publications/what-return-for-risk-the-price-of-equity-capital-in-public-privat. ↩↩

  4. Damodaran, A. Country Risk Premiums, Equity Risk Premiums, and Industry Betas, NYU Stern School of Business, updated annually. The platform's CAPM block reads the latest annual tables for risk-free rate, ERP, country default spread, and unlevered industry beta. https://pages.stern.nyu.edu/~adamodar/. ↩↩

  5. International Monetary Fund and World Bank. PPP Fiscal Risk Assessment Model (PFRAM 2.0), IMF/WB, 2019. The methodology Austral's FARO module implements for per-jurisdiction direct-commitment and contingent-liability aggregation against fiscal ceilings. https://www.imf.org/external/np/fad/publicinvestment/pdf/PFRAM.pdf. ↩

  6. Deb, K., Pratap, A., Agarwal, S., & Meyarivan, T. (2002). "A fast and elitist multiobjective genetic algorithm: NSGA-II." IEEE Transactions on Evolutionary Computation, 6 (2), 182–197. The Pareto-front endpoint runs a numpy-only re-implementation of this algorithm. https://doi.org/10.1109/4235.996017. ↩

Working on PPP fiscal management in a finance ministry or a development bank?
Let’s talk for 30 minutes — no sales pitch.
Book a call →
Read the manual: Value for Money and the Public Sector Comparator →
Lee el manual: Valor por Dinero y Comparador Público‑Privado →
Leia o manual: Value for Money e o Comparador do Setor Público →