P01
GARCH-EVT Risk Engine — Azure Deployment (Decommissioned 2026-10-15)
Azure SQL ServerlessTerraform
GitHub Actions OIDCPandera
Pytestarch
Ran unattended every day from 16 August to 4 September 2026 to answer one question for anyone exposed to
Finnish electricity prices —
how bad could a bad day get — and halted rather than publishing when the input series had a gap it
could not defensibly fill or the volatility model failed to converge.
The first out-of-sample backtest rejected 99% coverage — 8 breaches against 2.44 expected,
Kupiec p = 0.0047 — and the forecast was withheld. On the current pinned range: 14 breaches
against 14.05 expected at 95% over 281 days (Kupiec p = 0.99), with the 99% level reported
alongside its power caveat rather than as confirmation.
- That fix changed four things at once (threshold selector, rolling window, exceedance floor, evaluation
period). A later ablation cleared the selector and the floor; window length and evaluation period remain
confounded, and cannot be separated because the 2023 range no longer exists upstream. The original
single-cause attribution was wrong, and its replacement is narrower rather than conclusive. Both the
failure and the correction are in the devlog.
- Rebuilt a local research pipeline as a passwordless, zero-secret Azure deployment: Terraform-provisioned
Azure SQL Serverless, Blob-backed remote state, GitHub Actions authenticated via OIDC federated
credentials.
- Built an idempotent ingestion layer (staging table + T-SQL
MERGE) where source cadence is
not assumed (resampling the 15-minute market time unit to hourly means before Pandera contracts enforce a
gapless, timezone-aware index), and exponential backoff to survive Azure SQL Serverless cold starts (error
40613).
- Fitted a rolling GJR-GARCH(1,1,1) + Peaks-Over-Threshold (POT/GPD) EVT engine with daily Kupiec POF and
Christoffersen conditional-coverage tests, plus a raw Acerbi–Székely ES statistic that is reported rather
than thresholded (it follows no standard distribution); the pipeline halted on unfillable data gaps or
convergence failures rather than silently dropping days.
- Benchmarked against filtered historical simulation, a GARCH-t quantile, and an unconditional empirical
quantile over an identical 281-day scheme. No arm separates on coverage — including the unfiltered one —
so the honest reading is that the sample lacks the power to rank them; GARCH-EVT differs only in running
10–15% more conservative at 99%.
- Fixed three Azure/ODBC/SQLAlchemy production issues: OIDC subject-claim mismatch, driver auth keyword
collision, SQL type inference on tz-aware timestamps.
P02
Causal Pricing Engine
PythonDoubleML
LightGBMStreamlit
PytestMyPy
Tells a pricing team how much of their brand's share a price change would actually move,
separating real customer response from the brand loyalty and competitor discounting that make
raw sales data misleading — and lets them test a scenario in a dashboard before committing.
A 10-cent increase in sale price lowers the probability of choosing the brand by
~4.6 percentage points (θ̂₀ = −0.460, p < 0.001), holding loyalty and
competitor price and promotion fixed. Estimated on a linear probability scale, so this is a
local slope, not a calibrated probability model.
- Read as a local slope, not a dollar-scale prediction: the treatment (sale price) spans $1.39–$2.09, so a
full $1.00 move is outside the support of the data.
- Omitted-variable-bias sensitivity puts the robustness value at 12.3%: at worst-case alignment, an
unobserved confounder would need to explain ~12.3% of residual variance in both treatment and outcome to
null the point estimate — 8.1% to null significance. Not yet benchmarked against the observed covariates,
so it bounds fragility without establishing that no such confounder exists.
- Observations cluster by store and week but the model was fitted with i.i.d. standard errors, so the
p-value is understated; the point estimate is the defensible quantity.
- Estimated the effect with a partially linear DML model on the partialling-out score — LightGBM nuisance
learners, 5 folds × 5 repetitions.
PriceCH and SpecialCH are excluded together:
sale price is list price minus discount, and the discount is near-zero absent a promotion, so conditioning
on both leaves almost no residual treatment variation. The cost is that list-price level goes uncontrolled
— a stated trade-off, not a free lunch.
- Validated the estimator against a synthetic design with known θ₀ and a second with an analytically
derived omitted-variable bias, both asserted in CI alongside Ruff, MyPy (strict), and a locked dependency
graph.
- Built a typed
src/ layout with a Streamlit simulator that reads the pipeline artifact
rather than refitting.
P03
Adaptive Conformal Forecasting
Distribution-free prediction intervals for volatile time series
PythonMAPIE
DockerPlotly
PytestMyPy
Turns a single-number electricity demand forecast into a 90% prediction interval — the band the
outcome falls inside nine hours in ten, not a best case or a worst case — that planners can size
capacity and hedging decisions against. The band recalibrates itself against realized coverage as
the series shifts regime, instead of holding a width that stopped being correct.
On 1,500 held-out hours at a 90% target, the adaptive layer covered 89.9% with
intervals 7.5% narrower than a static conformal baseline that over-covered at 92.7% (sweep
evaluated on the reporting window, so the reduction is optimistically biased).
- Reported with its resolution: at n = 1,500 the binomial SE on coverage is ≈0.8 pp before correcting for
serially dependent misses, so 89.9% is indistinguishable from the 90% target — the separation from 92.7%
is the informative comparison, not the last decimal.
- The γ sweep behind that default was run on the same held-out window the numbers are reported on, so the
width reduction is optimistically biased; a three-way split is the first structural fix. γ = 0.005 gave a
larger reduction (10.9%) than the chosen 0.01, so the default is a judgment call on adaptation speed
rather than an optimum the sweep establishes.
- Above γ ≈ 0.03 the update overshoots after miscoverage events — coverage still tracks 90%, but mean
width inflates sharply (47% wider than static at γ = 0.05).
- Applied EnbPI with block-bootstrap residuals (MAPIE) in place of split conformal — a weaker assumption,
not no assumption: coverage becomes approximate and asymptotic under stationarity and mixing conditions
rather than finite-sample under exchangeability.
- Fixed an ACI update that ran once per 168-hour chunk on the chunk's aggregate error rate instead of per
timestep — roughly two orders of magnitude coarser than the Gibbs & Candès recursion. The α path is
now per-step; the emitted band stays piecewise-constant over the batching interval, so the displayed
interval is a downsampled view of it.
- Containerized the stack with Docker and wrote one end-to-end validation script covering install, tests,
lint, image build, and container health check.
P04
ESEF Machine-Readability Benchmark
PythonSQLite
XBRL / iXBRLPytest
MyPy (strict)Cluster bootstrap
EU-listed companies are required to file their annual accounts in a machine-readable format, so
software should be able to find the headline figures — revenue, profit, assets, equity, liabilities,
cash — without guessing. This measures how often it actually can, on every Finnish issuer that filed.
93.9% of 882 issuer-field cells carry the ESEF core standard element (95% cluster-bootstrap
CI 91.8%–95.7%, 147 issuer clusters, B = 10,000). The gap is not spread evenly: liabilities is
tagged in 79.6% of filings and revenue in 89.8%, while the other four sit above 98%.
- The dominant cause is legitimate presentation, not defective tagging: 30 of 147 issuers report equity
and liabilities split into non-current and current components, with no bare total line to tag. That is a
valid IAS 1 statement and a machine-readability gap at the same time, which is the finding.
- Excluded cells stay in the denominator. Conditioning on classifiability would have defined away part of
the thing being measured; the exclusions are published by reason code so a reader can apply their own
test.
- The pre-registered reason for clustering on the issuer was falsified by the data and is published as
falsified: shared templates and tagging vendors predict an intraclass correlation near 1, and the measured
value is 0.1099. The clustering is still correct; the argument for it was not.
- Stopped the planned LLM extraction arm on a measurement, before a single document was sent: the deepest
lossless redaction still left a mean 1,331,978-token document against a 1,050,000-token context window, at
$51.66 projected for this frame. Recorded VOID rather than FAIL — a gate whose inputs were never observed
has no verdict — and the scope change is a dated amendment, not a rewritten spec.
- Removed a verification script that ran the scripts writing its own inputs and then asserted PASS on
them, and traced a one-character constant corruption that disabled the entire redactor while
mypy --strict reported success. Three reasoned diagnoses were wrong; printing the bytes at
the failing offset settled it.
P05
Procurement Awards Against the Trade Register — Databricks and dbt
DatabricksUnity Catalog / Delta
dbtPython
Power BI (PBIP/TMDL)Pytest
When a Finnish public procurement award names a supplier and its business id, does the national
trade register hold a company under that id, and does its legal name agree with the name in the
notice? Measured for every contractor occurrence in one contiguous frame of Hilma award notices —
32,831 notices and 12,434 contractor occurrences, published 2023-03-09 to 2026-09-07 — against the
PRH trade register bulk, through a bronze/silver/gold dbt pipeline on Databricks Free Edition, a star
schema, and a Power BI model with dynamic row-level security on top.
Of the 11,479 occurrences that carry a business id, 81.56% (9,362) name a supplier
active at award whose register name agrees — exact, normalised or former — and 66.27%
(7,607) match exactly. Both are lower bounds: a supplier missing from the PRH snapshot counts
against them, and backfilling the 179 missing ids that resolve on the live PRH API gives measured
ceilings of 84.98% and 66.30%.
The Power BI report, refreshed from the dev
warehouse on 2026-09-25: whole-frame totals only.
- “Not in PRH” means absent from a snapshot, not unknown to the register: 1,206 fact rows over 585
business ids are missing from the 2026-09-07 bulk of 463,246 records. 179 of those ids resolve on the live
PRH API; in an earlier census of 559 of them, 164 of the 169 that resolved were ceased companies.
- Two Hilma endpoints, two id spaces, no error: fetching a search id of the form
EF-<n>
returned an unrelated migrated notice with HTTP 200, not 404, so an id list built from search would have
loaded wrong notices silently. The frame is therefore an id backfill, not a query by procurement category.
- Most name disagreement is real: of 884 rows whose register names all disagree with the notice, 825 rows
on 322 ids match no PRH name of any type, and 59 rows on 21 ids match only an auxiliary trade name (55) or
a parallel name (4), name types the model excludes.
- Every architecture choice follows from a measured platform constraint: extraction runs on the
workstation because serverless egress fails at DNS for Hilma, PRH and GLEIF; dbt runs as one job task
because job concurrency is 1; a provisioning script recreates the catalog before any run because catalogs
are not guaranteed durable; and no access token goes into CI because the SKU has no account-level API for
OIDC federation.
- 43 data tests and 2 unit tests; the last full
dbt build on the dev warehouse passed 56 of
56 nodes. The lineage and star-schema diagrams in the README are generated from tracked files, and a
tracked pre-commit hook rejects CR, NUL and secret patterns.
- Hilma payloads carry personal data and supplier names can include sole traders, so the extractor keeps
fields by a whitelist and the project publishes counts only: no supplier names and no business ids.
P06
Repository Standard and Release Control (repo-audit)
BashBubblewrap
GitLab CIGit bundles
Git hooksLinux / WSL
One repository standard for all five portfolio repositories: a shared byte-scan gate, a drift check
against the hub's copies, and a release procedure in which each change is developed, reviewed and
merged under separate Linux users.
The landing step runs no code from the task branch. It accepts a handed-off bundle only if the bundle's
head matches the handed-off commit exactly, and the merge goes through a merge request into a protected
main.
- Each role runs as its own Linux user in its own WSL distro: coding agent (Claude Code in an
rc.sh box
bwrap sandbox with filtered proxy), independent review (Antigravity in an isolated clone), and credentialed merge host.
- The
land subcommand integrates task bundles without executing task-branch code (Git hooks
disabled, no build scripts), verifies bundle ownership, and requires the bundle head to match the
handed-off commit SHA exactly.
- The D7 byte scan (
scripts/check_bytes.sh) checks tracked text files in CI and pre-commit
hooks, rejecting UTF-8 BOM markers, CR line endings, NUL bytes, and key-shaped secret patterns.
- Drift detection (
scripts/drift.sh) performs read-only checks comparing child clones against
the hub's canonical D7 files (.gitattributes, .githooks/pre-commit, and
scripts/check_bytes.sh), returning exit code 0 when synchronized and exit code 1 on any file
drift.
- Documented deviations from the standard: no SAST (the hub has no application code); CI only on
non-draft merge requests into
main; branch protection confirmed only by the settings UI
(push test skipped); CI image tag debian:trixie-slim pinned by digest.