Foundations for Deciding When to Use a Third-Party Guarantee

A quantitative decision framework for MIGA, IBRD, IDA, regional MDB, bilateral DFI, and ECA guarantees on infrastructure projects, implemented on the Austral Platform

Author: David Duarte Arancibia, Austral Intelligence Date: 5 May 2026  ·  Revised: 27 October 2026 (see the Revision note, Appendix F.3) Audience: Sponsors, project finance advisors, MDB credit officers, Ministry-of-Finance fiscal-risk teams, lenders Companion software: Austral platform — https://platform.austral-intelligence.com


Abstract

A third-party guarantee — from the Multilateral Investment Guarantee Agency (MIGA, World Bank Group), the International Bank for Reconstruction and Development (IBRD), the International Development Association (IDA), a regional multilateral development bank (MDB), a bilateral development finance institution (DFI), an export-credit agency (ECA), or a private insurer — can move an infrastructure project from "unbankable in the current market" to "investible." The cost is the guarantee fee. The decision is rarely posed as a calculation. It is usually treated as a heuristic ("this country needs Political Risk Insurance (PRI)") or a sales conversation with the lenders.

This paper takes the central planner's perspective — the Ministry of Finance or sector regulator asking whether the guarantee reduces the all-in cost paid by whoever ultimately funds the project (users in user-pays contracts, the host government in availability-payment contracts) by more than the fee. The framework runs the project finance (PF) model twice — once at market terms, once with the guarantee in place — and in each run solves for the tariff (or availability payment) that produces the sponsor's required equity Internal Rate of Return (IRR). Critically, the required IRR itself is not held fixed: when the guarantee transfers project risk to a guarantor, the sponsor's equity hurdle drops by Δhurdle = λ × Δspread (Δspread expressed as a fraction — e.g. 237 bps = 0.0237 — and product-specific λ ≈ 1.1–1.9, derived in Appendix E), so the planner captures both the lower debt cost and the lower required equity premium. The society's NPV saving — the difference in the all-in cost stream — is the right comparison to the fee. The decision rule is direct: take the guarantee if and only if society's saving is positive.

The framework distinguishes two cases. Feasibility-enabled — Run A is infeasible at any tariff: the project does not exist without the guarantee, and the decision is a cost-benefit-analysis (CBA) question outside the scope of this paper. Cost-reduction with rational pricing — both runs are feasible at some tariff: the framework computes society's saving directly. A third behavioural mode, sponsor capture, occurs only when contract structure prevents the savings from passing through; the framework flags this as a market-friction warning rather than treating it as a separate equilibrium.

The framework is implemented end-to-end on the Austral Platform. A worked example for a frontier-market US$ 500 M solar Independent Power Producer (IPP) closes the paper, ranking three candidates at 90 % coverage: an IBRD Project-Based Guarantee (society saving +US$ 527 M, the dominant verdict), MIGA Political Risk Insurance (+US$ 241 M), and an OECD-Arrangement ECA cover (+US$ 195 M, much the most tenor-sensitive of the three). The planner-view society savings are dramatically larger than the sponsor-view differentials — for MIGA-PRI the sponsor view says reject (−US$ 12 M) while the planner view says take (+US$ 241 M), because in equilibrium the lower hurdle delivers a tariff reduction that the sponsor-view framing entirely misses7.

Keywords: Multilateral guarantee, MIGA, IBRD, IDA, ECA, Political Risk Insurance, project finance, bankability, guarantee fee, OECD Arrangement, audit-evidence.

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


Table of contents

  1. The decision problem
  2. The iteration / method logic
  3. Stage 1 — Baseline bankability without external support
  4. Stage 2 — Counterfactual lender terms with a guarantee
  5. Stage 3 — Guarantee fee schedule
  6. Stage 4 — Re-solve the financial model with guarantee terms
  7. Stage 5 — Society's saving and the decision
  8. Architecture
  9. Walkthrough — reproduce in 10 minutes
  10. Worked example — Frontier-Market Solar Independent Power Producer (IPP)
  11. Limitations and roadmap

Appendices A, B, C, D, E, F follow the page break.


1. The decision problem

Why this matters: a third-party guarantee can be the difference between a project closing and a project never breaking ground; the fee is real cash that the project pays out year after year, so the decision should not be made on instinct.

A typical situation. A solar IPP with a US$ 500 M total cost in a frontier-market jurisdiction is presented to commercial lenders. The market response is a 600 basis points (bps) spread over the local benchmark, a 12-year tenor, and a 1.40× minimum Average Debt-Service Coverage Ratio (Min ADSCR) covenant. At those terms the equity Internal Rate of Return (IRR) drops below the sponsor's 18 % hurdle and the DSCR is uncomfortably tight, so the project is unbankable. With a MIGA Political Risk Insurance (PRI) cover at 90 % of the senior debt principal, the same lender offers 350 bps, an 18-year tenor, and a 1.30× DSCR covenant. MIGA charges roughly 1.4 % per annum on the covered amount, plus a 50 bps upfront fee at financial close1. The question is direct — is the project better off taking the guarantee, or rejecting it and either restructuring or walking away?

The same question arises for IBRD Project-Based Guarantees (PBGs)2, IDA PBGs2, International Finance Corporation (IFC) Partial Credit Guarantees (PCGs)3, Asian Development Bank (ADB) PCGs4, OECD-Arrangement Export Credit Agencies (ECAs)5, and private PRI providers (Lloyd's, AIG, Sovereign, Zurich). The fee schedules differ. The counterfactual debt-term reductions differ. The decision logic is identical.

Practitioners answer this question today using one of three modes9:

The framework in this paper replaces all three modes with a deterministic, replicable, audit-tracked computation.


2. The iteration / method logic

Why this matters: a one-shot "compare with vs without" is not enough — the variables feed back into each other, and a defensible recommendation has to handle that feedback explicitly.

2.1 Formal problem statement

Given a project specified by a Financial-Model ModelInputs payload with construction schedule, revenue stack, operating-cost stack, tax regime, and a senior commercial debt tranche priced at the current market.

Given a candidate guarantee product specified by a GuaranteeProduct triple — (provider, instrument, coverage_pct) — and a country-risk profile specified by a CountryRiskInputs triple — (country_code, OECD Country Risk Class (CRC) ∈ [0..7], sovereign_rating).

For each run (A — baseline; B — with guarantee), solve for the year-1 tariff (or availability payment) τ such that the sponsor's realised equity Internal Rate of Return (IRR) equals the run-specific required hurdle h:

Find society's NPV saving:

S = NPV(all-in cost stream A) − NPV(all-in cost stream B)

where the all-in cost stream in each run is the year-by-year payment society makes for the delivered services (tariff × demand in user-pays contracts; the availability payment in availability-payment contracts) discounted at the public discount rate. The fee is already inside Run B's all-in cost — Run B's tariff has to cover it — so S is net of the fee.

Subject to the bankability filters in each run:

The IRR test is by construction satisfied — the tariff bisection delivers it — so feasibility reduces to "does a feasible tariff exist?" and "does the DSCR covenant hold at that tariff?".

Apply the decision rule:

The differential equity NPV Δ = eNPV(B) − eNPV(A) − F is also computed and surfaced as a sponsor diagnostic. The sponsor view holds the equity hurdle fixed and so misses the channel through which risk transfer reduces the required equity premium; under the planner view that channel is captured by λ. The framework prints a market-friction warning when contract structure (e.g. directly-negotiated, fixed-tariff PPP with no re-bid) may prevent the equilibrium tariff cut from passing through to society — in that case the sponsor pockets the saving as private rent, and the planner should either reject the guarantee or fix the contract structure first.

2.2 Why iteration is unavoidable

The variables in the problem feed back into each other. No closed-form solution exists.

Variable Depends on Affects
Guarantee fee Outstanding covered debt amount, year by year OPEX line in Run B
Outstanding covered debt amount Senior debt amortisation schedule Annual fee stream → fee NPV
Senior debt amortisation schedule Lender terms — rate, tenor, repayment type DSCR, equity cash flow
Lender terms Whether the guarantee is present + coverage Whether bankability holds
Equity hurdle in Run B Δspread × λ, with floor h_floor The tariff that solves equity IRR = hurdle
Tariff (Run A and Run B) Bisection target — equity IRR = run hurdle Society's all-in cost stream
Coverage % of debt Decision variable in the outer loop Spread reduction, tenor extension, fee

Reading the table top-to-bottom: every variable in row N depends on a variable in a later row, and every variable affects something in an earlier row. The FM model is needed twice — once at market terms (Run A, with bisection on the tariff to hit h_A), once at guaranteed terms (Run B, with bisection on the tariff to hit h_B) — and the two runs share no shortcut.

2.3 The inner loop — bankability under guarantee terms

The inner loop is the Run B feasibility check. For a fixed (product, country, coverage_pct), it runs once and returns a Yes/No on bankability plus the snapshot ratios. The flow:

Inputsproduct · country · coverage_pct
Look up counterfactual terms
Look up fee schedule
Compute Run-B hurdle
Apply to FMInputs
Bisect tariff τBuntil IRRequity(τB) = hB
Extract from RunSnapshot
feasible_B= ADSCR_pass && tariff_in_bounds && FM checks

The loop has no recursion: each step uses the output of the previous step and never revisits an earlier one. Termination is guaranteed.

2.4 The outer loop — coverage and provider selection

The outer loop sweeps across guarantee products and coverage levels to find the best decision. Priority ordering is enforced top-down so the sponsor and the host government look at the right candidates first.

  1. Eligibility filter. Drop products that do not apply: IDA only for IDA-eligible countries, MIGA in its 191 member states1, ECAs only when the home-country exporter is involved, IBRD PBGs only in IBRD-eligible countries.
  2. Sovereign-credit cover first. If the project's binding constraint is a refusal-to-pay risk on a government payment obligation, prefer NHSFO or PBG-payment cover. The differential is typically large because the sovereign risk is the main spread driver.
  3. Project-credit cover next. Otherwise prefer PRI (transfer/expropriation/war/breach), PCG, or ECA cover. These transfer specific perils rather than the whole sovereign-credit story.
  4. Coverage sweep. For each shortlisted product, run the inner loop at coverage_pct ∈ {50 %, 75 %, 90 %}. The sweep is deliberately coarse — finer slicing rarely changes the decision. The worked example in §10 fixes coverage at 90 % across all candidates to keep the comparison readable; running the full sweep is a single API parameter change.
  5. Decision tabulation. Take the candidate with the highest society NPV saving S among those that pass bankability. Ties below 5 % go to the next tiebreaker.
  6. Tiebreaker. When two products are within 5 % of each other on society saving S, prefer (a) the one that delivers the larger bankability margin (DSCR headroom + Run B-tariff distance from the bracket bound), (b) the one with the more stable fee schedule (some private insurers can re-rate annually), (c) the one with the simpler underwriting process.

2.5 Why the method is not heuristic — convergence

The inner loop is deterministic. Every function in the chain — estimate_counterfactual_terms, compute_guarantee_fee, run_model — is a pure function of its inputs. There are no fixed-point iterations and no convergence tolerances to set.

The outer loop is bounded. Eligibility filtering is finite (at most ten provider × instrument pairs). The coverage sweep is finite (three values). Each (product, coverage) cell needs two tariff bisections (Run A and Run B), each ~10 FM solves at a baseline 100 ms per solve. The full sweep is therefore at most 10 × 3 × 2 × 10 = 600 FM solves, well under a minute on a single backend pod; a single decision (one product, one coverage) returns in 3–5 seconds on warm caches.

Society's saving S(coverage_pct) is well-behaved on the coverage axis:

S is the difference between the spread-and-hurdle benefit (concave in coverage, via the square-root law) and the fee cost (roughly linear in coverage), so it is quasi-concave with a unique maximum somewhere in the 50 %–90 % range for most products. The discrete sweep finds it.

2.6 The loss function

Under the planner view, the loss is the negative society saving. Minimisation maps to the decision.

def planner_loss(decision: SocietyDecision) -> float:
    """Loss to minimise — the more negative, the better the guarantee.

    Conventions:
      * A scenario with no revenue lever of the chosen contract type
        returns +infinity: neither run was built, so there is no saving
        to minimise. This is NOT a rejection of the instrument — the
        optimiser must skip it rather than score it (see §7).
      * Infeasible Run B returns +infinity — never take such a guarantee.
      * Bankability flip (Run A infeasible, Run B feasible) is handed to
        the CBA pipeline (outside this paper); the framework returns
        +infinity from the planner-loss perspective so the optimiser
        does not select it without explicit operator approval.
      * Otherwise return -society_saving_npv.
    """
    if decision.case == "not_applicable":
        return float("inf")  # nothing was modelled

    if not decision.guaranteed_run.feasible:
        return float("inf")

    if decision.case == "feasibility_enabled":
        return float("inf")  # defer to CBA pipeline

    return -decision.society_saving_npv

A guarantee is taken if and only if planner_loss < 0. The platform implements this rule inside evaluate_guarantee_central_planner, returning decision = "take" when society_saving_npv > 0, decision = "reject" when it is non-positive or Run B is infeasible, and decision = "not_applicable" when no run could be built at all. The legacy sponsor-view loss −Δ = −(eNPV(B) − eNPV(A) − F) is still computed and surfaced in the diagnostic panel; the two losses can disagree (see §10).

2.7 Termination semantics and audit-chain hook

Each evaluation — whether the planner-view call (evaluate_guarantee_central_planner) or the sponsor-view diagnostic (evaluate_guarantee):

Any auditor — internal compliance, supreme audit institution, MDB compliance — can re-run the same call against the saved inputs and verify the result matches the audit-chain entry to the cent.


3. Stage 1 — Baseline bankability without external support

Why this matters: the guarantee can only be assessed against an honest "what would happen if we tried to close the project at market terms today" — and that baseline has to come from a fully-built FM, not a back-of-envelope estimate.

Logic

The baseline run is the project as the lender market would price it today, with no third-party support. The senior commercial debt tranche carries the full risk premium for the country, the sector, and the contractual risk allocation. The DSCR covenant is whatever the lender market actually demands. There is no guarantee fee in OPEX.

Under the planner view the framework bisects the year-1 tariff (or availability payment) until the realised equity IRR equals the sponsor's market hurdle h_A. The output of Stage 1 is a RunSnapshot containing the solved tariff, the resulting min_adscr, avg_adscr, equity_npv, the year-by-year all-in cost stream society pays, and a feasible flag.

Either end of the bracket can be reached without a root, and the solver says which:

Neither outcome short-circuits the evaluation. The reason code travels on the decision as base_tariff_solver_reason and is turned into a sentence in the decision notes for the audit chain ("Run A, without the guarantee: the project already earns the required equity return at the bottom of the solver's search range, so the lowest user tariff in that range was used."), and the framework goes on to build Run B and returns whatever the Stage-5 rule of §7 gives. In the over-bankable case that rule never returns take: Run B searches the same tariff range as Run A — the range is set from the scenario's own tariff, which the guarantee does not change — so Run B can never solve below Run A's floor. Either both runs sit on that floor and S = 0, or Run B needs a higher tariff and S < 0; the verdict is reject by the general S ≤ 0 rule, not by an early exit. The one exception is a Run A that clears h_A at the floor but does not cover its debt service there: if the guarantee's debt terms cure that, Run B is feasible where Run A is not, and the case is the bankability flip of §7, take_with_caveat. The sponsor-view fallback (no bisection, fixed market tariff input) is also computed as the legacy diagnostic.

Austral tool: Financial Model — Inputs + Statements + Charts

The user builds the project in the Austral FM module — inputs, statements, charts, sensitivity pages of /workspaces/{wid}/fm/ — and saves the scenario to the workspace. The same ModelInputs payload feeds Stage 2.

Gate

The Run A bisection must converge inside the plausible tariff bounds. If even at the upper end of the bracket the project still fails the lender's DSCR covenant — Min ADSCR below 1.0 means debt service is not covered at any tariff society would tolerate — then nothing but a guarantee could make the project bankable, and the gate moves to Run B. The planner classifies on Run B, not on Run A. If Run B is feasible the case is feasibility_enabled and the verdict is take_with_caveat — the bankability flip of §7. If Run B fails too, the project is fundamentally infeasible, a guarantee will not fix it, and the framework reports case = "infeasible" with decision = "reject". The sponsor-view diagnostic has its own code for that last combination, infeasible_either_way; the planner view does not use it. In neither case does the evaluation stop early — both bisections run before any verdict is read.

Figure 01


4. Stage 2 — Counterfactual lender terms with a guarantee

Why this matters: the value of a guarantee comes from the change in the project's debt terms, and that change has to be calibrated against observed transactions, not assumed.

Logic

For a given (provider, instrument, coverage_pct) and (country_code, CRC, sovereign_rating), the framework looks up three empirical adjustments:

These adjustments are empirical — they come from observed MDB-backed transactions and from the published rate cards of the guarantors. The framework ships with a calibrated default table indexed by (provider × instrument × CRC). The defaults are mid-range estimates and can be overridden per transaction when the sponsor has lender pricing in hand.

A note on calibration: the counterfactual debt-term table is the input that drives most of the headline number, and the current values are seed estimates derived from public rate cards plus a small set of recent transactions. A formal back-test against a larger transaction sample (Senegal Senergy 2024, Bangladesh Summit 2023, Kenya Lake Turkana, IFC Mocoa 2022)8 is in progress and is tracked as an open item in §11.3 — until that lands, sponsors should treat the table as a calibrated guess and override per transaction whenever they have lender pricing.

The adjustments scale with coverage percentage on a square-root curve: bps_effective = bps_at_full_cover × sqrt(coverage_pct). The shape captures the empirical observation that the first 50 % of cover delivers most of the spread compression; further coverage has diminishing returns. Tenor extension is integer-valued and assumed flat in coverage above 50 %.

Austral tool: credit-enhancement analyzer (FM extension)

The frontend page at /workspaces/{wid}/fm/credit-enhancement/ cascades the provider selection into a list of valid instruments (MIGA → PRI / NHSFO; IBRD → PBG-loan / PBG-payment / PBG-policy; …) and surfaces the current counterfactual values to the user before any FM run is triggered. The user can override any value with a single field.

Gate

The looked-up counterfactual values must be plausible — spread reduction in [0, 600] bps, tenor extension in [0, 15] years, DSCR relaxation in [0, 0.20]. Values outside those ranges are flagged for manual review; outside the ranges suggests the reference table needs calibration for that country class.

Figure 02


5. Stage 3 — Guarantee fee schedule

Why this matters: the fee is the price of the guarantee, and a fee that is too aggressive will turn an otherwise valuable guarantee into a net cost.

Logic

The framework computes the guarantee fee in two parts:

The total fee NPV is the sum of the upfront fee at t=0 plus the discounted annual fee stream over the guarantee tenor (which equals base senior debt tenor plus the counterfactual tenor extension). The discount rate defaults to the project's public discount rate and can be overridden per transaction.

The default fee tables are calibrated from public sources:

Source Used for
MIGA Annual Reports + published rates1 MIGA-PRI, MIGA-NHSFO
WB Treasury — IBRD/IDA guarantee financial terms2 IBRD-PBG (loan / payment / policy), IDA-PBG
IFC public rate disclosures3 IFC-PCG
ADB / AfDB / IDB / EBRD published rates4 Regional MDB PCGs
OECD Arrangement minimum reference rates5 OECD-ECA
Practitioner observation Private PRI cover

The defaults are mid-range. Override per transaction when you have a quote in hand.

Austral tool: compute_guarantee_fee() engine function

The platform's FM credit-enhancement page surfaces the upfront fee, annual fee rate, the per-year annual fee stream, and the total fee NPV next to the counterfactual-terms panel — all derived from the chosen (provider, instrument, country.risk_class, coverage_pct, tenor) combination, with a single text input letting the user override the rate when needed.

Gate

Under the planner view the fee is inside Run B's all-in cost stream — Run B's bisected tariff has to cover it. The natural gate becomes: society's saving (= Run A all-in cost − Run B all-in cost, both NPVs) must be positive. The framework returns reject whenever it is not. The legacy sponsor-view gate (fee NPV must be less than baseline equity NPV in Run A) is still computed as a diagnostic and surfaces the early-warning case where the fee alone exceeds the project's equity value — which can occur with very high country risk, very long tenor, and high coverage.

Figure 03


6. Stage 4 — Re-solve the financial model with guarantee terms

Why this matters: the guaranteed-case equity returns and bankability ratios cannot be estimated by hand — they require a full re-run of the cash-flow waterfall under the new terms.

Logic

The framework deep-copies the baseline ModelInputs, applies the counterfactual changes (lower senior debt rate, longer senior debt tenor, relaxed Min ADSCR covenant) and adds the average annual fee as an insurance_cost line in OPEX. It then bisects the Run B tariff until the realised equity IRR equals the Run B hurdle h_B = max(h_A − λ × Δspread, h_floor). Each bisection step is one call to ppp_core.engine.model.run_model; convergence is typically 6–10 iterations.

The result is a second RunSnapshot (Run B) with the solved tariff, the resulting Min ADSCR, the year-by-year all-in cost stream society pays at the new tariff, and the same equity IRR and equity NPV at the new hurdle. The same ModelInputs schema is used in both runs, so the FM canonical engine produces ratios that are directly comparable to Run A.

Three nuances matter for clean comparison:

Austral tool: Financial Model run_model — bisected invocation

Internally each Run B candidate tariff is fed through run_model(inputs_B(τ)) where inputs_B(τ) = _apply_guarantee_terms(inputs_A, counterfactual, fee, τ). The bisection loop runs ~10 times before settling on the tariff that produces equity IRR = h_B. Run A bisects analogously against h_A. The two outer bisections are independent and could be parallelised; the current implementation runs them sequentially and the full evaluation typically completes in ~1 second.

Gate

The Run B bisection must converge inside the plausible tariff bounds (a configurable interval, default 0.1× to 10× the Run A solved tariff). If the bisection bracket is exhausted — the tariff that hits h_B lies outside the plausible bounds, or the cash flows are negative throughout — Run B is treated as infeasible and the framework recommends reject. The same applies in reverse to Run A.

Figure 04


7. Stage 5 — Society's saving and the decision

Why this matters: this is the step where the framework returns a recommendation a Ministry of Finance, a sector regulator, or a sponsor can defend in front of an investment committee.

Logic

The primary verdict is society's NPV saving:

S = NPV(all-in cost stream A) − NPV(all-in cost stream B)

The all-in cost stream in each run is the year-by-year payment society makes for the delivered services (tariff × demand in user-pays contracts; the availability payment in availability-payment contracts) discounted at the public discount rate. Run B's tariff already covers the guarantee fee, so S is net of the fee. The decision rule:

Revenue lever of the chosen contract type Run A feasible Run B feasible Society saving S case decision
Absent — (no run built) — (no run built) None — never computed not_applicable not_applicable
Present (any) No ignored infeasible reject
Present No Yes ignored feasibility_enabled take_with_caveat
Present Yes Yes > 0 cost_reduction take
Present Yes Yes ≤ 0 cost_reduction reject

The four rows that build runs are tested in that order, and the order matters: a scenario with no lever also leaves Run B infeasible, so the lever test has to come first or the two collapse into one. (One residual branch: if both runs are feasible but a cost stream cannot be recovered from one of them, S is None and the verdict is reject with a note saying the saving could not be computed. It has not been observed outside a deliberately broken model.)

The bankability-flip case is qualitatively different. When Run A is infeasible, the project does not exist without the guarantee — there is no Run A all-in cost to compare against, because society pays nothing for a project that never enters operations. The verdict in that case is a cost-benefit-analysis (CBA) question: is the project worth doing at Run B's society cost, given whatever non-financial benefits it delivers? That CBA is outside the scope of this paper. The framework reports case = "feasibility_enabled" with decision = "take_with_caveat" and surfaces Run B's society cost so the planner can hand the question to the standard CBA pipeline.

The not-applicable case is the one outcome that is not a verdict on the guarantee at all. Both tariff bisections read the revenue lever of the chosen contract type before they run anything: revenue.base_tariff under user_pays, the first availability payment's annual_amount under availability. If that lever has no positive value to scale — a user-toll scenario evaluated as an availability contract, say, or an availability scenario whose payment schedule is still empty — the solver returns no_base_lever_value immediately and neither run is ever built. There is no Run A cost, no Run B cost, no solved tariff on either side and no saving: society_saving_npv is None, not a negative number. The framework reports case = decision = "not_applicable" and says so in the decision notes, in the engine's own words — here for a user-toll scenario evaluated as an availability contract:

The tariff solver had nothing to scale: this scenario has no availability payment for the chosen contract type. Add one, or switch to the user-pays contract type.

This is not a rejection: neither run could be built, so the planner test was not run.

The distinction matters for anything that counts. reject is a verdict on an instrument that was evaluated: both runs were built, priced and compared, and the guarantee did not pay for itself. not_applicable means the comparison never happened, and the usual cause is a scenario configured for the other contract type rather than anything about the guarantee. A screening exercise that pooled the two would report a provider as having failed a test it was never put through, and a coverage sweep that minimised rejections would be optimising over an empty set — which is why the loss function of §2.6 skips the case rather than scoring it. The platform keeps the two apart end to end: the decision card prints the verdict in neutral grey rather than the red reserved for reject, both solved-tariff rows read "no lever to solve" rather than "infeasible", and the export endpoints, when sent a planner decision, label it NOT APPLICABLE (in grey on the PDF); the DOCX replaces the decision rationale with the missing-lever explanation and how to fix it.

The sponsor-view differential Δ = eNPV(B) − eNPV(A) − F is also computed and surfaced as a diagnostic. Δ is what the sponsor sees if they hold the equity hurdle fixed at h_A and ignore the equilibrium tariff cut delivered by the lower hurdle. As §10 demonstrates, Δ and S can have opposite signs for the same deal — the planner view says take; the sponsor view says reject. When Δ is negative but S is positive, the framework prints a market-friction warning: the saving only flows to society if contract structure (re-bid, regulated tariff, or competitive renewal) lets the equilibrium tariff cut pass through. In a directly-negotiated, fixed-tariff PPP with a monopoly sponsor and no re-bid, the sponsor pockets the saving as private rent, and the planner should either reject the guarantee or fix the contract structure first.

Austral tool: credit-enhancement decision card

The platform renders the verdict prominently, colour-coded on the card's left border and on the verdict line itself: green for take, red for reject, amber for take_with_caveat (the bankability flip, which routes the user to the CBA pipeline rather than to a take/reject answer), and neutral grey for not_applicable — deliberately not red, because no instrument failed a test here. Beside it sit society's saving (the headline number, omitted when there is none to show), the Run A and Run B all-in cost streams, the counterfactual terms, the fee breakdown, the sponsor diagnostic Δ in a collapsible panel below, and any market-friction warning. The card is designed to be lifted directly into a Ministry-of-Finance or investment-committee paper.

Gate

The verdict + the supporting numbers (Run A snapshot, Run B snapshot, counterfactual terms, fee breakdown, society saving, sponsor diagnostic, market-friction warning) form a single, exportable record. The DOCX export endpoint produces a methodology note that includes the verdict, the rationale, and the audit-chain ID. The PDF export produces a one-page dashboard. The XLSX export produces a workbook for the deal team to inspect line by line.

Figure 05


8. Architecture

Why this matters: the framework's value rests on producing the same answer regardless of who runs it, when, or where — and on being audit-traceable from end to end.

The framework operates in a single mode: a thin overlay on the FM module's canonical cash-flow engine. There is no "alternative engine" to choose between. Two entry points are exposed in ppp_core.engine.credit_enhancement:

Both functions are shared by the platform module and (in principle) by any future Streamlit standalone — guaranteeing that the platform UI, the public API, and any downstream automation return identical numbers.

Per-jurisdiction awareness. The fee tables and counterfactual-term tables are indexed by OECD Country Risk Class (CRC) on the 0–7 scale, with each provider × instrument pair mapping to its own row. A change in CRC (e.g. country upgrade or downgrade) flows through to fees and to counterfactual terms automatically. Sovereign rating is captured in the CountryRiskInputs for reference but does not currently drive the lookup — it is present so users can record the rationale.

Multi-currency. When the FM model runs in dual-currency mode (financing currency vs local currency), the credit-enhancement engine inherits the currency conversion automatically. The fee is denominated in the financing currency (which is the convention all major guarantors use). The exposure of the project to local-currency depreciation, when relevant, can be captured separately via the existing FM multicurrency extension.

Audit chain. Every call — evaluate_guarantee_central_planner (planner view) or evaluate_guarantee (sponsor diagnostic) — writes an immutable entry to the workspace activity log: input hash, output hash, user identity, timestamp, server-side run identifier. The activity log is SHA-256 hash-chained — each entry's hash includes the previous entry's hash — so any tampering breaks the chain at the modified entry and downstream. Re-running the same call against the same inputs produces the same output; the audit chain ID lets a future auditor verify that without running anything.

Signed PDFs. The DOCX, XLSX, and PDF exports embed the audit-chain ID and the decision hash at the bottom of every page. When the document leaves the platform — emailed to a sponsor's investment committee, attached to an MDB submission — the receiver can verify the document has not been altered by re-running the underlying evaluation against the recorded inputs and comparing hashes. Optional cryptographic signing (PAdES for PDF) can be enabled per workspace; the signing infrastructure is shared with the existing FCCL Reporting module's signed output.

The full API and pseudocode reference is in Appendix D.


9. Walkthrough — reproduce in 10 minutes

Why this matters: the framework is only useful if a practitioner can follow eight discrete steps from a blank workspace to a defensible decision in under 10 minutes.

Step 1 — Open the platform and create a workspace. Navigate to https://platform.austral-intelligence.com/. The platform creates a Guest workspace automatically; no signup is required for the smoke test.

Figure 06

Step 2 — Build the baseline FM scenario. Open the Financial Model module via the left sidebar. Use the Inputs page to define construction CAPEX, revenue, OPEX, tax, depreciation, the senior commercial debt tranche (rate, tenor, repayment type), and the equity contribution. Save the scenario.

Figure 07

Step 3 — Sanity-check Run A. Open Statements and Ratios. Confirm the project is plausibly viable at market terms — Min ADSCR not catastrophically below 1.0, cash-flow profile reasonable. The credit-enhancement page will bisect the tariff in Step 8 to hit the planner-supplied hurdle, so the question here is just whether the project deserves a guarantee analysis at all.

Figure 08

Step 4 — Open the Credit Enhancement page. Click "Credit Enhancement" in the FM sidebar (or navigate to /fm/credit-enhancement).

Figure 09

Step 5 — Pick the source FM scenario. Use the dropdown to select the scenario you saved in Step 2.

Step 6 — Configure the guarantee product. Select Provider (e.g. MIGA), Instrument (e.g. PRI), and Coverage % (e.g. 90 %). The instrument list auto-filters to those valid for the selected provider.

Figure 10

Step 7 — Configure the country risk profile. Enter the country code, OECD CRC (0–7), and sovereign rating. The CRC drives the fee schedule and the counterfactual terms via the reference tables.

Step 8 — Set the planner inputs and click "Evaluate (planner view)". Enter the base equity hurdle h_A and the contract type (user-pays or availability-payment), then click the primary CTA. Direct-negotiation contracts are flagged separately by the market-friction warning rather than as a contract-type value. The decision card shows the society saving as the headline number, the Run A and Run B all-in cost streams, the solved tariffs in each run, the counterfactual terms, the fee breakdown, and the verdict (take / reject / take_with_caveat for the bankability flip / not_applicable when the scenario carries no revenue lever of the contract type you chose — the usual cause is picking "availability payment" for a user-toll scenario, or the reverse). The sponsor-view differential is collapsed in a "Sponsor diagnostic" panel below the planner verdict — expand it to see Δ at fixed hurdle. A market-friction warning appears in amber when the contract type may prevent the saving from passing through. Iterate by changing provider or coverage and clicking Evaluate again — each evaluation takes ~1 second and writes a new audit-chain entry.

Figure 11


10. Worked example — Frontier-Market Solar Independent Power Producer (IPP)

Why this matters: a concrete numerical example shows the framework in action and lets the reader sanity-check the recommendation against intuition.

Project parameters (baseline FM inputs):

Variable Value
Total CAPEX US$ 500 million
Installed capacity 500 MW (solar, ~22.8 % capacity factor)
Construction period 2 years
Operating period 20 years
Tax rate 25 %
Depreciation life 20 years (straight-line)
Year-1 generation 2,740 MWh / day
Tariff (year 1) US$ 100.00 / MWh
Annual demand growth 3.0 %
Annual tariff escalation 2.0 %
Operating cost (year 1) US$ 15 million / year
OPEX escalation 2.5 %
Senior commercial debt share 70 % of CAPEX
Senior commercial debt rate 10.0 %
Senior commercial debt tenor 15 years (linear amortisation)
Equity 30 % of CAPEX
Public discount rate 8 %
Country profile CRC 6, sovereign rating B
Sponsor's equity IRR hurdle (h_A) 18 %
Equity-hurdle floor (h_floor) 7 % (Appendix E)
Min ADSCR target 1.30 ×

Run A — baseline (no guarantee).

The framework bisects the year-1 tariff until the realised equity IRR equals the 18 % hurdle. At that tariff:

Metric Value
Solved year-1 tariff US$ 96.10 / MWh
Society's all-in cost (NPV @ 8 %) US$ 1,284 million
Equity IRR (achieved) 18.0 %
Min ADSCR 1.53 ×
Feasible YES

The project is bankable at market terms — DSCR comfortably above 1.30, equity hurdle hit by construction. The total cost society pays is US$ 1,284 million in NPV terms over the 20-year operating life. The planner now asks: would a third-party guarantee bring this number down by more than its fee?

Candidate 1 — MIGA PRI at 90 % coverage.

The framework first looks up the counterfactual debt-term changes (237 bps spread reduction, 6-year tenor extension, 0.05× DSCR relaxation). With λ = 1.30 — MIGA-PRI transfers only the four political-risk perils, not full sovereign credit — the equity hurdle drops by 1.30 × 237 / 10 000 ≈ 3.08 percentage points. The hurdle floor of 7 % is not binding. The framework solves the Run B tariff at the new 14.92 % hurdle:

Metric Value
Counterfactual spread reduction 237 bps
Counterfactual tenor extension 6 years
λ (equity-elasticity of risk transfer) 1.30
Δhurdle (= λ × Δspread) −3.08 percentage points
Equity hurdle (Run A → Run B) 18.00 % → 14.92 %
Solved year-1 tariff (Run B) US$ 78.10 / MWh
Society's all-in cost (NPV @ 8 %) US$ 1,043 million
Total fee NPV (informational) US$ 30 million
Society saving +US$ 241 million
Decision (planner) TAKE
Sponsor's perceived NPV uplift −US$ 12 million (diagnostic)

The sponsor view and the planner view point in opposite directions for this deal. The sponsor view (hurdle held fixed at 18 %) sees a US$ 12 million loss because the MIGA fee swallows the equity-NPV uplift the sponsor would notice if the tariff stayed at US$ 96.10. The planner view sees a US$ 241 million saving because the lower hurdle, in equilibrium, allows a tariff cut from US$ 96.10 to US$ 78.10 — a 19 % reduction. The all-in cost society pays falls from US$ 1,284 million to US$ 1,043 million.

The sponsor view's "reject" verdict is wrong from the planner's seat. The right answer is take — and the only situation where it would be wrong is the sponsor-capture case: a directly-negotiated, fixed-tariff contract where the sponsor pockets the equilibrium savings as monopoly rent rather than passing them through to society. The framework prints a market-friction warning when that risk is present so the planner can either reject the guarantee or re-open the tariff.

Candidate 2 — IBRD PBG-loan at 90 % coverage.

IBRD's PBG transfers full sovereign-credit risk; λ = 1.90, the highest in the catalogue. The hurdle drops by 1.90 × 389 / 10 000 ≈ 7.39 percentage points — from 18.00 % to 10.61 %, well above the 7 % floor. Tariff is solved at the new hurdle:

Metric Value
Counterfactual spread reduction 389 bps
Counterfactual tenor extension 13 years
λ (equity-elasticity of risk transfer) 1.90
Δhurdle (= λ × Δspread) −7.39 percentage points
Equity hurdle (Run A → Run B) 18.00 % → 10.61 %
Solved year-1 tariff (Run B) US$ 56.60 / MWh
Society's all-in cost (NPV @ 8 %) US$ 757 million
Total fee NPV (informational) US$ 8 million
Society saving +US$ 527 million
Decision (planner) TAKE
Sponsor's perceived NPV uplift +US$ 64 million (diagnostic)

The IBRD PBG is dramatically the better instrument: society saves US$ 527 million versus US$ 241 million for MIGA-PRI. Both effects compound — IBRD's spread reduction is 1.6× larger than MIGA-PRI's (389 vs 237 bps), AND λ is higher (1.90 vs 1.30) because IBRD takes full sovereign-credit risk while MIGA-PRI takes only the political-risk perils. The combined effect is a 7.4 pp hurdle reduction versus 3.1 pp, and the tariff drops from US$ 96.10 to US$ 56.60 — a 41 % reduction.

The sponsor view's diagnostic figure (+US$ 64 million) is roughly 12 % of the planner-view society saving. That asymmetry is the core insight: in equilibrium the sponsor captures none of the value (they are indifferent at hurdle = required return). All US$ 527 million flows through to whoever funds the project.

Candidate 3 — OECD-Arrangement ECA at 90 % coverage, 23-year tenor.

Metric Value
Counterfactual spread reduction 209 bps
Counterfactual tenor extension 8 years
Total guarantee tenor (base 15 + 8) 23 years
Counterfactual DSCR relaxation 0.047 ×
λ (equity-elasticity of risk transfer) 1.40
Δhurdle (= λ × Δspread) −2.92 percentage points
Equity hurdle (Run A → Run B) 18.00 % → 15.08 %
Annex VIII tenor multiplier (23 yrs) 2.50 × (cap)5
MPR base (CRC 6, 5-yr baseline)5 1.50 % p.a. (illustrative)
MPR effective (after multiplier) 3.75 % p.a.
Solved year-1 tariff (Run B) US$ 81.47 / MWh
Society's all-in cost (NPV @ 8 %) US$ 1,089 million
Total fee NPV (informational) US$ 82 million
Society saving +US$ 195 million
Sponsor's perceived NPV uplift −US$ 90 million (diagnostic)
Decision (planner) TAKE

OECD-Arrangement ECA cover is the only instrument in the catalogue where the OECD's Annex VIII tenor multiplier is binding on the fee schedule5. At a 23-year guarantee tenor the multiplier hits its 2.50 × cap, lifting the effective annual MPR from 1.50 % to 3.75 % per annum on the covered amount. The fee NPV swells to US$ 82 million — bigger than the headline spread-compression gain a sponsor would notice at fixed hurdle, which is why the sponsor diagnostic is sharply negative (−US$ 90 M). The planner view still returns a solid society saving (+US$ 195 M) because the hurdle reduction allows a tariff cut from US$ 96.10 to US$ 81.47. The framework's audit-chain note for this evaluation reads: "OECD Arrangement tenor multiplier 2.50× applied (tenor 23 yrs); λ = 1.4 capped by ECA's partial credit-risk transfer (Appendix E)."

The lesson for ECA cover is structural: the Arrangement deliberately makes long-tenor ECA cover expensive so participating ECAs do not undercut the multilateral guarantor pool5. Here the fee is by far the largest of the three candidates — US$ 82 M against MIGA-PRI's US$ 30 M and IBRD-PBG's US$ 8 M — and it is what separates the two views so violently: the sponsor diagnostic is the worst of the three (−US$ 90 M) while the planner's saving (+US$ 195 M) lands within a fifth of MIGA-PRI's (+US$ 241 M) and well below IBRD-PBG's (+US$ 527 M). The gap between ECA and MIGA cover is a margin, not a gulf: at CRC 6 the Arrangement's inflated premium is almost entirely absorbed by a spread compression (209 bps) close to MIGA-PRI's (237 bps). What makes the instrument fragile is tenor, not country class. Practitioners structuring deals with ECA backing should target tenors at or below the project's debt maturity — outside that band, the Arrangement's pricing can turn ECA cover from value-creating to value-destroying, and the verdict flips. The MPR base is shown as an illustrative CRC-6 calibration; the actual OECD-published rate for CRC 6 should be substituted whenever the live snapshot is available (see §11.2 OECD rate-snapshot infrastructure).

Iteration trace. The framework ran three evaluate_guarantee_central_planner calls — one per candidate. Each call ran two tariff-bisection sweeps (Run A and Run B) over the FM canonical engine, plus an evaluate_guarantee baseline for the sponsor diagnostic. Total: roughly 90 FM solves in ~9 seconds at the 100 ms-per-solve baseline; the platform is faster on warm caches and typically returns in 3–5 seconds. The decision ranking is unambiguous: take the IBRD PBG when available (society saving +US$ 527 M); take MIGA-PRI when IBRD eligibility does not apply (+US$ 241 M); take ECA cover third (+US$ 195 M, and much the most tenor-sensitive of the three).

The deeper insight. From the central-planner's seat, the primary value of a third-party guarantee is not the equity-NPV uplift the sponsor would notice — it is the all-in cost reduction society pays for the same delivered services. That cost reduction comes from two sources, both flowing to whoever funds the project: (i) the lower commercial debt cost the lender accepts, and (ii) the lower equity premium the sponsor accepts in equilibrium because risk has been transferred away7. The framework's λ-based estimate of Δhurdle captures the second channel — a channel the sponsor-view framing entirely misses.

Two practical corollaries fall out:

  1. The sponsor-view differential and the planner-view saving can have opposite signs. MIGA-PRI in this worked example is rejected by the sponsor view but taken by the planner view. The sponsor sees a fee that exceeds an equity-NPV uplift held artificially small by a fixed hurdle; the planner sees a tariff reduction that exceeds the fee by ~8×.

  2. Contract structure matters as much as guarantee choice. When the contract structure prevents the equilibrium savings from flowing to society — a directly-negotiated, fixed-tariff PPP with a monopoly sponsor and no re-bid — the sponsor pockets the planner's saving as private rent. The framework prints a market-friction warning when that structure is detected. The planner's response is either to reject the guarantee or to fix the contract structure first; the response is not to evaluate the guarantee in isolation.


11. Limitations and roadmap

Why this matters: every framework has assumptions; documenting them protects users from drawing the wrong conclusions when the underlying assumptions are violated.

11.1 What this version assumes

11.2 What has shipped

11.3 What remains open

Appendix A — Reproducible API call

Run the worked-example evaluation against the deployed platform.

Step 1: get a guest token.

TOKEN=$(curl -s -X POST https://platform.austral-intelligence.com/api/v1/auth/guest \
  -H "Content-Type: application/json" -d '{}' \
  -A "Mozilla/5.0" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
WID=$(curl -s -X POST https://platform.austral-intelligence.com/api/v1/auth/guest \
  -H "Content-Type: application/json" -d '{}' \
  -A "Mozilla/5.0" | python3 -c "import sys,json; print(json.load(sys.stdin)['workspace_id'])")

Step 2: post the planner-view evaluate request.

Download the request body, published beside this paper, then post it:

curl -s -o worked_example_body.json \
  https://austral-intelligence.com/research/third-party-guarantee-decision/worked_example_body.json
curl -s -X POST \
  "https://platform.austral-intelligence.com/api/v1/workspaces/$WID/fm/credit-enhancement/evaluate-cp" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -A "Mozilla/5.0" \
  -d @worked_example_body.json

The worked_example_body.json payload is the FM ModelInputs dict (matching the Stage-1 baseline parameters in §10) plus a product dict (provider, instrument, coverage_pct), a country dict (country_code, risk_class, sovereign_rating), a base_equity_hurdle (e.g. 0.18), and a contract_type ("user_pays" or "availability"); the FM inputs travel under the key model_inputs. The file is published beside this paper (above) and kept with its sources, together with scripts/worked_example.py, which calls the same engine function the route wraps — without the route's live OECD rate overlay — and needs no token (Appendix F.3).

Step 3: parse the response.

import json
r = json.loads(response_body)["result"]
print(r["case"])                           # "feasibility_enabled" | "cost_reduction"
                                           #   | "infeasible" | "not_applicable"
print(r["decision"])                       # "take" | "reject" | "take_with_caveat"
                                           #   | "not_applicable"
print(r["society_saving_npv"])             # planner-view headline, in financing-currency millions
print(r["base_equity_hurdle"])             # h_A
print(r["guaranteed_equity_hurdle"])       # h_B (after λ × Δspread, clamped at floor)
print(r["base_tariff_solved"])             # τ_A
print(r["guaranteed_tariff_solved"])       # τ_B
print(r["sponsor_view_eNPV_uplift"])       # diagnostic Δ — sign can disagree with society_saving_npv
print(r["market_friction_warning"])        # non-null if contract structure may leak the saving

On a not_applicable response every field that would have come out of a run is null — society_saving_npv, both solved tariffs, both all-in costs and the market-friction warning — and base_tariff_solver_reason and guaranteed_tariff_solver_reason both read "no_base_lever_value". A client that treats not_applicable as a rejection is counting a test that was never run; branch on it explicitly.

The legacy sponsor-only call lives at POST .../credit-enhancement/evaluate and returns a GuaranteeDecision with differential_equity_npv instead — kept for backwards compatibility but not the recommended entry point.


Appendix B — Adapter reference

The credit-enhancement engine takes three inputs and returns one structured output.

Inputs.

from ppp_core.engine.model_inputs import ModelInputs
from ppp_core.engine.credit_enhancement import (
    GuaranteeProduct, CountryRiskInputs,
)

inputs = ModelInputs(...)                    # FM canonical schema
product = GuaranteeProduct(
    provider="IBRD",                          # 'MIGA' | 'IBRD' | 'IDA' | 'IFC' | 'ADB' | ...
    instrument="PBG-loan",                    # depends on provider
    coverage_pct=0.90,                        # 0..1 share of senior debt covered
)
country = CountryRiskInputs(
    country_code="ZZ",
    risk_class=6,                             # OECD CRC 0..7
    sovereign_rating="B",                     # S&P scale, reference only
)

Primary output — SocietyDecision (returned by evaluate_guarantee_central_planner).

@dataclass
class SocietyDecision:
    case: str                                 # 'feasibility_enabled' | 'cost_reduction'
                                              #   | 'infeasible' | 'not_applicable'
    decision: str                             # 'take' | 'reject' | 'take_with_caveat'
                                              #   | 'not_applicable' — the planner test
                                              #     was not run; NOT a rejection
    contract_type: str                        # 'user_pays' | 'availability'

    # Hurdles
    base_equity_hurdle: float                 # h_A
    guaranteed_equity_hurdle: float           # h_B = max(h_A − λ·Δspread, h_floor)
    hurdle_reduction_pp: float                # Δhurdle in percentage points
    lambda_used: float                        # λ from the product table

    # Run A — without guarantee
    base_tariff_solved: float | None
    base_tariff_solver_reason: str | None     # None on a clean root; else one of
                                              #   'no_base_lever_value' |
                                              #   'model_failed_at_high_lever' |
                                              #   'infeasible_at_high_lever' |
                                              #   'already_above_target' |
                                              #   'max_iter_reached'
    base_run: RunSnapshot
    total_cost_society_a_npv: float | None    # NPV of revenue stream A at public discount rate

    # Run B — with guarantee
    guaranteed_tariff_solved: float | None
    guaranteed_tariff_solver_reason: str | None   # same vocabulary as Run A's
    guaranteed_run: RunSnapshot
    total_cost_society_b_npv: float | None    # NPV of revenue stream B (fee already inside)

    # Society saving + verdict
    society_saving_npv: float | None          # = a_npv − b_npv. None if either side infeasible.

    # Counterfactual + fee (informational)
    counterfactual: CounterfactualTerms
    fee: GuaranteeFeeBreakdown

    # Sponsor diagnostic
    sponsor_view_eNPV_uplift: float | None    # Δ at fixed hurdle — diagnostic only
    market_friction_warning: str | None       # populated when sign(Δ) ≠ sign(S)
    notes: list[str]                          # audit-chain provenance

Legacy output — GuaranteeDecision (returned by the sponsor-view evaluate_guarantee).

@dataclass
class GuaranteeDecision:
    baseline: RunSnapshot                     # Run A — without guarantee
    guaranteed: RunSnapshot                   # Run B — with guarantee
    counterfactual: CounterfactualTerms
    fee: GuaranteeFeeBreakdown
    differential_equity_npv: float            # Δ = eNPV(B) − eNPV(A) − F
    bankability_flip: bool                    # True if Run A infeasible & B feasible
    decision: str                             # 'take' | 'reject' | 'infeasible_either_way'
    binding_constraint: str                   # 'none' | 'min_adscr' | 'equity_irr_*' | ...
    notes: list[str]

RunSnapshot carries equity_irr, min_adscr, avg_adscr, project_irr, equity_npv, and feasible. CounterfactualTerms carries spread_reduction_bps, tenor_extension_years, dscr_relaxation, and provenance notes. GuaranteeFeeBreakdown carries upfront_fee_amount, annual_fee_pct, annual_fee_stream (year by year), fee_npv, and the discount rate used.


Appendix C — Glossary


Appendix D — API and pseudocode reference

Endpoints (all under /api/v1/).

Method Path Purpose
GET /workspaces/{wid}/fm/credit-enhancement/products Catalogue of supported products
POST /workspaces/{wid}/fm/credit-enhancement/evaluate-cp Planner-view evaluation (primary)
POST /workspaces/{wid}/fm/credit-enhancement/projects/{pid}/eval-cp Planner-view on saved FM scenario
POST /workspaces/{wid}/fm/credit-enhancement/evaluate Sponsor-view diagnostic (legacy)
POST /workspaces/{wid}/fm/credit-enhancement/projects/{pid}/eval Sponsor-view on saved FM scenario
GET /workspaces/{wid}/fm/credit-enhancement/scenarios List saved CE scenarios
POST /workspaces/{wid}/fm/credit-enhancement/scenarios Save scenario
GET /workspaces/{wid}/fm/credit-enhancement/scenarios/{sid} Load scenario
DELETE /workspaces/{wid}/fm/credit-enhancement/scenarios/{sid} Delete scenario
POST /workspaces/{wid}/fm/credit-enhancement/ai/insights Claude commentary
POST /batch/fm/credit-enhancement-eval Public batch (single)
POST /batch/fm/credit-enhancement-batch Public batch (up to 100)

Pseudocode — planner view (evaluate_guarantee_central_planner).

def evaluate_guarantee_central_planner(
    inputs, product, country, *,
    base_equity_hurdle=0.18,
    contract_type="user_pays",
    discount_rate=None,
    overlay=None,
):
    discount_rate = discount_rate or inputs.funding.npv_discount_rate or 0.08

    # Counterfactual + fee lookups (overlay first, fall back to seed table)
    cf = estimate_counterfactual_terms(product, country, overlay=overlay)
    fee = compute_guarantee_fee(
        product, country,
        covered_amount=_covered_amount(inputs, product),
        fee_tenor=inputs.funding.tranches[0].total_term + cf.tenor_extension_years,
        discount_rate=discount_rate,
    )

    # λ-based hurdle adjustment, clamped at the floor
    lam = _HURDLE_REDUCTION_FACTOR_BY_PRODUCT.get(_key(product), 1.5)
    delta_spread = cf.spread_reduction_bps / 10_000.0
    h_B = max(base_equity_hurdle - lam * delta_spread, _EQUITY_HURDLE_FLOOR)

    # Run A — bisect tariff against h_A
    snap_a, tariff_a, reason_a = solve_tariff_for_target_irr(
        inputs, target_hurdle=base_equity_hurdle, contract_type=contract_type,
    )
    a_npv = _revenue_total_npv(_with_tariff(inputs, tariff_a), discount_rate)

    # Run B — bisect tariff against h_B, on guaranteed inputs
    inputs_b = _apply_guarantee_terms(inputs, cf, fee)
    snap_b, tariff_b, reason_b = solve_tariff_for_target_irr(
        inputs_b, target_hurdle=h_B, contract_type=contract_type,
    )
    b_npv = _revenue_total_npv(_with_tariff(inputs_b, tariff_b), discount_rate)

    # Society saving — primary verdict
    saving = (a_npv - b_npv) if (a_npv is not None and b_npv is not None) else None

    # Case classification, in this order. The no-lever test comes FIRST:
    # it also leaves snap_b infeasible, and the two are not the same thing —
    # one is a scenario that was never modelled, the other a project that was.
    if reason_b == "no_base_lever_value":
        case, decision = "not_applicable", "not_applicable"
    elif not snap_b.feasible:
        case, decision = "infeasible", "reject"
    elif not snap_a.feasible:
        case, decision = "feasibility_enabled", "take_with_caveat"
    else:
        case = "cost_reduction"
        decision = "take" if (saving is not None and saving > 0) else "reject"

    # Sponsor diagnostic (Δ at fixed hurdle) — for the friction warning
    sponsor_uplift = (snap_b.equity_npv or 0.0) - (snap_a.equity_npv or 0.0) - fee.fee_npv
    friction = _market_friction_warning(saving, sponsor_uplift, contract_type)

    return SocietyDecision(
        case=case,
        decision=decision,
        contract_type=contract_type,
        base_equity_hurdle=base_equity_hurdle,
        guaranteed_equity_hurdle=h_B,
        hurdle_reduction_pp=(base_equity_hurdle - h_B) * 100,
        lambda_used=lam,
        base_tariff_solved=tariff_a, base_run=snap_a, total_cost_society_a_npv=a_npv,
        guaranteed_tariff_solved=tariff_b, guaranteed_run=snap_b, total_cost_society_b_npv=b_npv,
        society_saving_npv=saving,
        counterfactual=cf, fee=fee,
        sponsor_view_eNPV_uplift=sponsor_uplift,
        market_friction_warning=friction,
        notes=cf.notes + fee.notes,
    )

Pseudocode — sponsor-view diagnostic (evaluate_guarantee, legacy).

def evaluate_guarantee(inputs, product, country, *, discount_rate=None):
    discount_rate = discount_rate or inputs.funding.npv_discount_rate or 0.08

    # Sizing: covered amount = senior debt amount × coverage_pct
    capex = (
        inputs.general.construction_cost
        + inputs.general.land_cost
        + inputs.general.design_cost
        + inputs.general.other_cost
    ) or inputs.general.total_investment
    senior_debt_amount = capex * inputs.funding.debt_pct * inputs.funding.tranches[0].share
    covered_amount = senior_debt_amount * product.coverage_pct

    # Counterfactual + fee lookups
    cf = estimate_counterfactual_terms(product, country)
    base_tenor = inputs.funding.tranches[0].total_term
    fee_tenor = base_tenor + cf.tenor_extension_years
    fee = compute_guarantee_fee(
        product, country, covered_amount, fee_tenor,
        discount_rate=discount_rate,
    )

    # Run A — baseline
    result_a = run_model(inputs)
    snap_a = _snapshot(result_a, discount_rate)

    # Run B — with guarantee
    inputs_b = _apply_guarantee_terms(inputs, cf, fee)
    result_b = run_model(inputs_b)
    snap_b = _snapshot(result_b, discount_rate)

    # Differential
    differential = (snap_b.equity_npv or 0.0) - (snap_a.equity_npv or 0.0) - fee.fee_npv
    flip = (not snap_a.feasible) and snap_b.feasible

    # Decision
    if not snap_b.feasible:
        decision = "infeasible_either_way" if not snap_a.feasible else "reject"
    elif differential > 0 or flip:
        decision = "take"
    else:
        decision = "reject"

    return GuaranteeDecision(
        baseline=snap_a, guaranteed=snap_b,
        counterfactual=cf, fee=fee,
        differential_equity_npv=differential,
        bankability_flip=flip,
        decision=decision,
        binding_constraint=...,
        notes=cf.notes + fee.notes,
    )

Reference-table seed sources. The default fee tables and counterfactual-term tables in ppp_core.engine.credit_enhancement are seeded from public sources: MIGA Annual Reports + published rate cards1, World Bank Treasury fee schedule for IBRD / IDA guarantees2, IFC public rate disclosures3, ADB / AfDB / IDB / EBRD published rates4, and OECD Arrangement minimum reference rates5. The seed values are mid-range estimates and are intentionally easy to override per transaction.


Appendix E — Derivation of the equity-hurdle elasticity λ

The whole framework's primary verdict turns on the relationship Δhurdle = λ × Δspread (with Δspread expressed as a fraction). This appendix derives that relationship, explains why λ is product-specific, gives the calibration table, and motivates the equity-hurdle floor h_floor = 7 %.

E.1 The structural argument

Let the project's required equity return decompose into a risk-free rate, a market risk premium, and a project-specific premium for project equity beta:

\[h \;=\; r_f \;+\; \beta_{equity} \cdot ERP\]

where ERP is the equity risk premium (≈ 5–6 % for emerging markets). The project's equity beta itself decomposes into the unlevered asset beta and a leverage multiplier:

\[\beta_{equity} \;=\; \beta_{asset} \cdot \Bigl[\, 1 + \tfrac{D}{E}(1 - t) \,\Bigr]\]

The senior commercial debt spread compensates lenders for the same underlying project risk, attenuated by the seniority premium and by the lender's recovery expectation. To first order:

\[\Delta\text{spread}_{debt} \;=\; \sigma \cdot \rho_{debt} \cdot \beta_{asset}\]

where σ is the asset-volatility scale and ρ_debt is the share of debt-side variance that flows through to lender pricing. The same underlying β_asset change drives equity-side compression:

\[\Delta h_{equity} \;=\; \beta_{asset, removed} \cdot ERP \cdot \Bigl[\, 1 + \tfrac{D}{E}(1 - t) \,\Bigr]\]

Dividing the equity-side compression by the debt-side compression yields:

\[\lambda \;=\; \frac{\Delta h_{equity}}{\Delta\text{spread}_{debt}} \;=\; \frac{ERP \cdot \bigl[\, 1 + \tfrac{D}{E}(1-t) \,\bigr]}{\sigma \cdot \rho_{debt}}\]

So λ is high when (i) the equity risk premium is large, (ii) leverage is high, (iii) the share of risk that flows through to the debt is small (because ρ_debt in the denominator is small). It is low when the guarantee transfers risk that primarily affects debt-service (e.g. transfer/convertibility) but leaves the equity beta nearly unchanged.

E.2 Why λ is product-specific

The risk a guarantee transfers is not a scalar — it is a bundle of perils. Different products transfer different bundles:

Product Perils transferred Equity-side bite λ
IBRD-PBG-loan Full sovereign-credit risk on debt service Very high — sovereign credit is the dominant equity-beta driver in frontier markets 1.90
IDA-PBG-loan Same as IBRD-PBG, on IDA-eligible (lower-income) countries Very high — same structural transfer as IBRD-PBG 1.90
MIGA-NHSFO Non-honouring of sovereign financial obligations (debt service) High — but more narrowly debt-side than IBRD-PBG 1.80
IFC-PCG Partial credit on a tranche of senior debt Medium-high — IFC's preferred-creditor umbrella also benefits equity 1.50
ADB-PCG Partial credit on a tranche of senior debt Medium-high — analogous to IFC-PCG 1.50
OECD-ECA Commercial + political risk on the export-financed portion only Medium — Annex VIII pricing reflects partial transfer 1.40
MIGA-PRI Four political-risk perils only (transfer, expropriation, war, breach) Lower — leaves commercial / demand risk with the sponsor 1.30
PRIVATE-PRI Same perils as MIGA-PRI but with insurer balance-sheet risk and re-rating Lowest — discounts for re-rating risk and counterparty 1.10

The ranking is what the structural model predicts: the more of the equity-relevant risk a guarantee transfers, the higher λ. IBRD-PBG sits at the top because it transfers full sovereign credit, which in frontier markets is the single largest contributor to project equity beta. Private PRI sits at the bottom because the sponsor must reserve a discount for the insurer's own credit and re-rating risk.

E.3 Calibration

The values in §10 and the table above are the platform's current seed values, locked by the calibration test suite (backend/tests/test_credit_enhancement_calibration.py). They were chosen to (i) preserve the structural ranking implied by the perils-transferred analysis, (ii) reproduce the order of magnitude of observed equity-hurdle compression in a small set of recent transactions (Senegal Senergy 2024, Bangladesh Summit 2023, Kenya Lake Turkana, IFC Mocoa 2022)8, and (iii) fall comfortably below the upper bound implied by [1 + (D/E)(1−t)] · ERP / σ, which for typical frontier-market parameters (D/E = 70/30, t = 25 %, ERP = 6 %, σ ≈ 8 %) sits around λ ≈ 2.5.

A formal back-test of λ against a larger transaction sample is the subject of the open calibration round documented in papers/CE_CALIBRATION_NOTES.md and surfaced in §11.3.

E.4 The equity-hurdle floor h_floor

The structural formula is linear in Δspread and in principle can drive h_B arbitrarily low. Empirically that is not what happens: even on heavily de-risked transactions, equity sponsors still demand a minimum premium for sponsor effort, illiquidity, and idiosyncratic operational risk that no guarantee covers. The framework imposes:

\[h_B \;=\; \max\bigl(\, h_A - \lambda \cdot \Delta\text{spread},\;\; h_{floor} \,\bigr), \qquad h_{floor} = 7\%\]

The 7 % figure is calibrated as r_f + minimum_equity_premium ≈ 4 % + 3 %, where the 3 % minimum equity premium is the lower bound observed across mature-market (CRC 0–2) infrastructure equity transactions. In the §10 worked example the floor never binds: IBRD-PBG drops the hurdle to 10.61 %, MIGA-PRI to 14.92 %, ECA to 15.08 %. The floor only binds in extreme cases — heavily-covered guarantees in low-risk countries — where it corrects an otherwise unrealistic Run B hurdle.

E.5 Sensitivity

The headline society-saving figures are reasonably robust to ±20 % perturbations of λ, because society's saving depends on the combined effect of the spread compression (in the empirical counterfactual table) and the hurdle compression (via λ). For the §10 IBRD-PBG candidate, λ = 1.5 instead of 1.9 reduces society's saving from US$ 527 M to roughly US$ 410 M — still a strong TAKE; λ = 0.7 (i.e. closer to a no-equity-channel assumption) would reduce it to ~US$ 220 M. Only at λ ≈ 0 — i.e. assuming the equity hurdle does not move at all — does the planner verdict collapse onto the sponsor diagnostic. Practitioners with a credible alternative calibration of λ should override per transaction; the framework propagates the override through the audit chain.


Appendix F — Errata and revision notes

23 October 2026 — the planner's fourth verdict, and what Stage 1 does at the ends of its bracket. Two corrections, both to the description of what evaluate_guarantee_central_planner returns. No number in this paper changed on that date. The §10 worked example (then IBRD-PBG +US$ 527 M, MIGA-PRI +US$ 241 M, OECD-ECA +US$ 94 M), the λ sensitivity of §E.5, the reference fee and counterfactual tables and every figure were unaffected; both corrections are about how outcomes are named and when the evaluation stops. §10's third candidate was corrected four days later — see the Revision note in F.3.

F.1 not_applicable was missing from the verdict set

Versions of this paper issued before this date listed three planner verdicts (take, reject, take_with_caveat) and three cases (feasibility_enabled, cost_reduction, infeasible). There are four of each. §2.1's decision rule and §7's decision table also named two codes the planner never returns — infeasible_either_way (the sponsor view's) and bankability_flip (a sponsor-view flag; the planner reports that case as take_with_caveat). Both now show the engine's own case/decision pairs.

A scenario that carries no revenue lever of the chosen contract type — no user tariff under a user-pays contract, no availability payment under an availability contract — makes both tariff bisections return no_base_lever_value before either run is built. The platform used to report that as reject. It now reports case = decision = "not_applicable", on the ground that "reject" is a verdict on an instrument that was evaluated, and here nothing was: neither run exists, so there is no saving, no solved tariff and no comparison. The decision notes say it in words — "This is not a rejection: neither run could be built, so the planner test was not run." — the decision card prints the verdict in neutral grey rather than the red reserved for a rejection, and anything that counts rejections must exclude it.

The new verdict is described in §7 and now appears in the decision rule of §2.1, the loss function of §2.6, the decision table and card description of §7, the walkthrough step of §9, the response and dataclass listings of Appendices A and B, the pseudocode of Appendix D, and the shipped list of §11.2.

F.2 Stage 1 does not stop early

§3 used to state that when even the lower bound of the plausible tariff range produces an equity IRR above h_A, the framework "returns decision = "reject" early". It does not, and never did.

What the solver does at that end of the bracket is report already_above_target and adopt the bottom of the bracket as the solved tariff. The reason code travels on the decision and is turned into a sentence in the audit notes, but the evaluation continues: Run B is built and priced, and the verdict comes out of the Stage-5 rule like any other. When both runs end up pinned to the same bracket floor the saving is exactly zero and the verdict is indeed reject — by the general S ≤ 0 rule, not by a short circuit. It can never come out take: both runs search the same tariff range, so Run B never solves below Run A's floor and S is never positive here. (The only other outcome is the bankability flip, when Run A clears the hurdle at the floor but does not cover its debt service there and the guarantee cures that.) The old text named the right verdict and the wrong mechanism.

The same section's Gate paragraph carried two related errors. It attributed infeasible_either_way to the planner view; that code belongs to the sponsor-view diagnostic evaluate_guarantee, and the planner reports case = "infeasible" with decision = "reject" instead. And it implied the planner classifies on Run A's feasibility; it classifies on Run B's — a Run A that fails at the top of its bracket while Run B succeeds is the bankability flip, not an infeasibility.

Both corrections were found by regenerating the paper against the engine rather than against the previous draft.

F.3 Revision note — 27 October 2026

§10's third candidate was labelled CRC 6 and priced off the CRC-5 row. The worked example states a single country profile — CRC 6, sovereign rating B — and candidates 1 and 2 use it. Candidate 3 did not: its counterfactual debt-term cells were the engine's CRC-5 values for OECD-ECA-ECA cover, carried under the CRC-6 heading. The row has been re-measured at CRC 6 and now reads as below; the last line is a separate correction to Run A, explained further down:

Metric Before After
Counterfactual spread reduction 171 bps 209 bps
Counterfactual tenor extension 7 years 8 years
Total guarantee tenor 22 years 23 years
Δhurdle (= λ × Δspread) −2.39 pp −2.92 pp
Equity hurdle, Run B 15.61 % 15.08 %
Solved year-1 tariff, Run B US$ 89.04 / MWh US$ 81.47 / MWh
Society's all-in cost, Run B US$ 1,190 m US$ 1,089 m
Total fee NPV US$ 80 m US$ 82 m
Society saving +US$ 94 m +US$ 195 m
Sponsor's perceived NPV uplift −US$ 76 m −US$ 90 m
Run A (no guarantee) — Min ADSCR 1.59 × 1.53 ×

Only the old row's debt-term cells (spread, tenor, Δhurdle, Run B hurdle) and its sponsor diagnostic were the engine's CRC-5 values; its tariff, all-in cost, fee and saving matched the engine at neither class. Every cell of the row is now engine output.

The verdict is unchanged — take, and third of the three candidates — but the margin is not: at CRC 6 the saving is a fifth below MIGA-PRI's rather than less than half of it, so the "take ECA cover only as a last resort" reading of the old row has gone, and the paragraph that drew it has been rewritten. λ (1.40), the covenant relaxation (0.047 ×), the illustrative MPR base (1.50 % p.a.) and the binding Annex VIII multiplier (2.50 ×, still at its cap at 23 years) are unaffected. Outside §10, the Abstract's one-line ranking carries the new saving and §E.4's list of Run B hurdles the new 15.08 %; the 23 October entry above keeps the §10 figures as they then stood. Apart from Run A's Min ADSCR, corrected below, no other number in the paper changes, and candidates 1 and 2 are untouched.

What now makes §10 reproducible. The request body Appendix A posts, worked_example_body.json, had not been kept with the paper, so §10 could not be re-measured. It has been reconstructed from §10's parameter table and is now published beside the paper (Appendix A) and kept with its sources, together with scripts/worked_example.py, which runs the three candidates through evaluate_guarantee_central_planner and prints each cell beside its published value. Two inputs the table does not state were inferred and are pinned by the fit: an even 50/50 construction drawdown and the platform's default equity-first draw order — any other split or draw order moves every figure of Run A and of candidates 1 and 2.

Running that script also dates the example. §10's figures reproduce on the engine as it stood from the paper's first release until early July 2026: Run A solves at US$ 96.08 / MWh against the published US$ 96.10 at an all-in cost of exactly US$ 1,284 m, and every US$ m figure of candidates 1 and 2 reproduces exactly, their tariffs to within US$ 0.04 / MWh. The figures above are measured on that same engine, so §10 remains internally consistent. Later fixes to how the engine finances the construction period move the whole example: re-measured on the current engine, Run A and all three candidates change, IBRD-PBG's verdict included, and MIGA-PRI's sponsor diagnostic changes sign. That re-measurement is a re-authoring of the example, not an erratum, and is not made here. One §10 figure did not reproduce on any version of that engine: Run A's Min ADSCR, published as 1.59 ×, measures 1.53 ×. It now reads 1.53 ×; the project still clears the 1.30 × covenant comfortably.

Figures 01 to 05 have been re-captured. All five showed the top of the credit-enhancement form rather than the panel their section describes — figure 01 was byte-identical to figure 09 and figure 02 to figure 04 — because the capture script located each result panel by a caption string that is not on the page and, on a miss, silently fell back to a generic page shot. Figure 01 is now the baseline financial statements of §3, 02 the counterfactual and Δhurdle panel of §4, 03 the fee breakdown of §5, 04 the Run A / Run B comparison of §6 and 05 the decision card of §7. The script now anchors on the panels' real headings and raises rather than substituting a different picture. Figures 06 to 11, which were navigated to by URL, were already correct and are unchanged.



  1. Multilateral Investment Guarantee Agency (MIGA), MIGA Annual Report 2024, World Bank Group, 2024. Membership figures and Political Risk Insurance (PRI) and Non-Honoring of Sovereign Financial Obligations (NHSFO) fee structure. https://www.miga.org/annual-report. ↩↩↩↩

  2. World Bank Treasury, IBRD Project-Based Guarantees and Policy-Based Guarantees — Financial Terms, World Bank Group, 2023. Front-end fee, annual guarantee fee, standby fee, and IDA guarantee terms for low-income countries. https://treasury.worldbank.org/en/about/unit/treasury/ibrd/ibrd-financial-products/project-based-guarantees. ↩↩↩↩↩↩

  3. International Finance Corporation, IFC Annual Report 2024, World Bank Group, 2024. Partial Credit Guarantee (PCG) product disclosures and pricing principles. https://www.ifc.org/en/annual-report. ↩↩↩

  4. Asian Development Bank, Guarantees — Financing Partnerships and Risk Mitigation Instruments, Asian Development Bank, 2023. Fee structure for ADB Partial Credit and Political Risk Guarantees; analogous documents from AfDB, IDB, and EBRD provide regional-MDB benchmarks. https://www.adb.org/what-we-do/financing-instruments. ↩↩↩

  5. Organisation for Economic Co-operation and Development (OECD), Arrangement on Officially Supported Export Credits — Annex VIII (Project Finance), document TAD/PG(2024)1, OECD Trade and Agriculture Directorate, 2024. Minimum Premium Rate (MPR) methodology and the tenor multiplier capped at 2.50× for tenors above 20 years. https://www.oecd.org/trade/topics/export-credits/arrangement-and-sector-understandings/. ↩↩↩↩↩↩↩

  6. OECD, Country Risk Classifications of the Participants to the Arrangement on Officially Supported Export Credits, OECD, updated quarterly. Eight-class scale (0 lowest risk to 7 highest) used as the index for fee schedules and counterfactual-term tables. https://www.oecd.org/en/topics/sub-issues/credit-risk-and-rating/country-risk-classification.html. ↩

  7. Engel, E., Fischer, R., and Galetovic, A., "The basic public finance of public-private partnerships," Journal of the European Economic Association, 11 (1), 2013, pp. 83–111. Establishes the equilibrium-pricing result that the equity hurdle adjusts when project risk is transferred, so risk-allocation gains accrue to the buyer of services rather than to the sponsor. https://doi.org/10.1111/j.1542-4774.2012.01105.x. ↩↩

  8. World Bank, Private Participation in Infrastructure (PPI) Database, World Bank Group. Used as the back-test base for counterfactual-term calibration; specific transactions cited (Senegal Senergy 2024, Bangladesh Summit 2023, Kenya Lake Turkana, IFC Mocoa 2022) are catalogued there. https://ppi.worldbank.org/. ↩↩↩

  9. APMG International with the World Bank, Asian Development Bank, Inter-American Development Bank, European Bank for Reconstruction and Development, and Islamic Development Bank, PPP Certification Program Guide — Chapter 8: Risk Identification, Allocation, and Management, 2016. Standard reference for the practitioner modes (heuristic, lender pitch, spreadsheet comparison) discussed in §1. https://ppp-certification.com/. ↩

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: Government Guarantees and Contingent Liabilities from All Sources →
Lee el manual: Garantías y Pasivos Contingentes de Todo Origen →