Solar, tender, price and legal data is public but fragmented and difficult to turn into a prospect a developer can act on.
From public data to a defensible energy prospect.
A map-first product for finding photovoltaic opportunities, applying public tenders and answering cited questions about tariffs and law. The architecture keeps model reasoning useful while moving consequential calculations, access control and evidence into deterministic services.
What was built, and what it proves
This is the shortest useful path for an interviewer. Everything below adds implementation evidence; nothing is hidden behind tabs or drawers.
One map combines roofs and sites, tender applicability, economics and a cited assistant.
Scheduled Python producers feed PostGIS/pgvector; a bounded .NET agent composes five tools.
Numbers come from code, legal evidence from Lex MCP, SQL is read-only and unsupported answers are refused.
The model plans; bounded services decide
The online path is deliberately small. The model can select tools and explain outputs, but it cannot write data, calculate a tariff or order the interface to display unsupported results.
sqlRead-only analysis and geo candidates; the exact query is provenance.
lawDated Luxembourg and reviewed EU text through public Lex MCP.
search_corpusHybrid retrieval over current energy documents in PostgreSQL.
compute_tariffTyped inputs into deterministic code; the model never calculates.
find_offersCurrent tenders and machine-checkable applicability constraints.
One question, five visible stages
Small because one engineer operates it
Static Web Apps serves the client. Container Apps runs the API. PostgreSQL Flexible Server owns relational, geospatial and vector data. Azure OpenAI handles chat and vision; Content Understanding extracts tender constraints. Terraform defines infrastructure; GitHub Actions deploys through OIDC.
A second search store or more agents must earn their operating cost through measured need.
Batch producers create reusable facts
Ingestion does expensive interpretation once. Requests read typed, provenance-carrying rows rather than repeatedly asking a model to rediscover structure.
Official and public sources
- National INSPIRE buildings + solar cadastre
- TED and Luxembourg tenders
- PVGIS, prices, tariffs and aid
- Creos documents + public Lex MCP
Python producers
- Coordinate transforms + geometric joins
- Three-class existing-PV vision
- Content Understanding extraction
- History, provenance and fail-loud status
PostgreSQL consumers
- PostGIS roofs, parcels and materialized sites
- Offers and typed constraints
- FTS + pgvector document chunks
- Read-only API tools and map endpoints
National buildings became the primary source
The first design used the solar cadastre and silently missed large industrial roofs. National building polygons now lead; the cadastre is a nullable suitability signal joined spatially. Unknown remains unknown.
yes / no / maybe, never forced certainty
Ambiguous roofs stay maybe; an outage leaves the field null. Every downstream surface preserves that distinction.
Documents become filterable predicates
Content Understanding stores verbatim text plus typed constraints. “Apply tender” is deterministic filtering, not query-time invented eligibility.
Energy documents here; temporal law in Lex
The current energy corpus uses PostgreSQL FTS + pgvector with rank fusion. Legal search is not duplicated: Lex provides dated Luxembourg and reviewed EU law, history, comparison and provenance.
Controls sit where the failure happens
Prompt instructions help, but consequential controls are enforced in code, database permissions, typed contracts or deployment gates.
| Failure | Live control | Residual risk |
|---|---|---|
| Fabricated tariff | Deterministic engine + C#/Python parity; model only extracts and narrates. | New formulas require code; uncovered questions are refused. |
| Unsafe database access | analyst_ro, forbidden-column bouncer, timeout and row cap. | A permitted SELECT may still be slow, wrong or infer data. |
| Confident false absence | Zero rows trigger witness queries and an explicit diagnosis. | Main and witness queries can share a wrong assumption. |
| Ungrounded prose | Session-wide evidence guard; judge sees actual rows, not citation labels. | Rendered rows have no separate prose-grounding score. |
| Wrong UI action | Typed payload shape drives pins/cards; no model render tool. | Bad aliases and coordinates still need validation. |
| Hidden uncertainty | Vision abstains to maybe; estimates and unknowns remain labelled. | A human must confirm ambiguous roofs. |
Transparent, bounded decision support
The interface identifies the AI. It provides research support, not automated decisions about people. Legal and monetary answers are cited or refused; inferred labels require confirmation.
Minimal anonymous session surface
No account or profile is required. Conversation turns support bounded continuity and are inaccessible to the analytical role. Inputs are not used for fine-tuning. A formal notice and erasure path remain gaps.
Passwordless CI, not a false zero-secret claim
GitHub deploys through OIDC. Runtime credentials are Container App secrets; the PostgreSQL bootstrap password remains in protected Terraform state. Managed-identity runtime auth is the next hardening step.
Appropriate for public data, not enterprise isolation
HTTPS, a restricted CORS allowlist and PostgreSQL firewall are live. Private endpoints, VNet integration, per-user authorization and retrieval ACLs are not.
One decision surface, not fourteen interruptions
Only the decisions that explain the product’s shape stay here. Full alternatives, evidence and reversal history remain in the repository.
Models never produce consequential numbers
- Instead of
- Compute with a model, then verify.
- Cost
- Every formula needs code and tests.
- Reverse if
- Refusal makes normal economics questions unusable.
One agent over five contract-driven tools
- Instead of
- One routed expert per topic.
- Cost
- Tool schemas become critical infrastructure.
- Reverse if
- Ordinary questions repeatedly hit the round cap.
Generic SQL finder under structural controls
- Instead of
- A rigid filter API.
- Cost
- Role, bouncer and performance need ownership.
- Reverse if
- Leakage or cost survives those controls.
Content Understanding for tenders
- Instead of
- PDF scraping plus another extractor.
- Cost
- Managed-service and model compatibility risk.
- Reverse if
- Constraint errors make Apply unreliable.
Postgres hybrid retrieval before AI Search
- Instead of
- A second search service from day one.
- Cost
- Retrieval quality is owned in-house.
- Reverse if
- Golden-set hit@5 falls below 0.80.
OIDC and Terraform delivery
- Instead of
- Stored CI credentials and portal state.
- Cost
- More identity and IaC setup discipline.
- Reverse if
- Never; extend managed identity to runtime.
National buildings supersede cadastre
- Instead of
- Patching a source missing valuable roofs.
- Cost
- Migration, re-derivation and spatial join.
- Reverse if
- Source noise outweighs regained coverage.
Benchmark model tiers; reject premium
- Instead of
- Assuming a larger model is better.
- Cost
- Token spend and evaluation work.
- Reverse if
- A larger independent-judge run reverses it.
All decision records in GitHub ↗ · Flagship numerical-safety decision →
Claims are paired with something inspectable
Deterministic behavior is tested on every change. Hosted-model evaluations run before releases; they are not misrepresented as per-commit gates.
| Model · July 2026 | Groundedness | Relevance | Refusal recall | Median tokens | p50 |
|---|---|---|---|---|---|
gpt-5-nano | 0.79 | 0.93 | 14 / 16 | 10,822 | 40.7 s |
gpt-5-mini production | 0.76 | 0.94 | 15 / 16 | 10,227 | 17.0 s |
gpt-5 | 0.77 | 0.83 | 15 / 16 | 14,534 | 48.3 s |
Caveats: one 40-question run; gpt-5-mini was the judge; candidate capacity differed, so latency is directional. The evidence rejected the premium tier and retained mini because refusal behavior matters most here.
Every push
.NET release build, tariff parity, Python tests, schema coverage and frontend tests/build.
Before release
Groundedness, outcome, law retrieval and agent behavior against hosted models.
After deployment
Immutable image, OIDC login, health smoke, public telemetry and source freshness.
A credible personal production system, not enterprise theatre
The left is live. The right is the regulated or multi-tenant target. Adding unused Azure logos would not close these gaps.
Real today
- Public deployment and live telemetry
- Automated pipelines with freshness status
- Read-only and deterministic tool boundaries
- Terraform, OIDC and tested CI/CD
- Measured model and retrieval choices
- Visible abstention, refusal and provenance
Enterprise adoption adds
- Entra runtime identity, private endpoints and VNet
- Per-principal authorization and retrieval ACLs
- Purview-class catalog, retention and lineage governance
- DPIA, model-risk approval and security review
- SLOs, load tests, on-call and disaster recovery
- Segregation of duties and release approvals
| Accepted risk | Why accepted here | Trigger / next control |
|---|---|---|
| Runtime secrets remain. | Public read-only workload; CI credential risk was removed first. | Managed identity before private or enterprise data. |
| Model-written SELECT may infer or be wrong. | Generality is useful and visible data is public. | Templates or per-user views when private data appears. |
| Model evals are pre-release. | They cost hosted tokens and wall time. | Automate after higher release frequency or a regression. |
| No permanent UAT environment. | A personal, public-data product cannot justify duplicate standing resources. Pull requests must pass deterministic gates; main deploys an immutable image, then a live health smoke confirms it. | Add an isolated integration/UAT stage, approvals and segregation of duties when a team, private data, stateful migrations or an SLA makes blast radius material. |
| Single-region app and database. | Personal traffic cannot justify redundant standing cost. | Test regional recovery when an SLA requires it. |
| Global cost guard may stop the app. | On a personal subscription, solvency outranks uptime. | Service budgets and chargeback before commercialization. |
The signal is judgment, not service count.
This demonstrates problem framing, data engineering, bounded agent design, RAG, Azure delivery, evaluation and honest risk ownership. The strongest choices are the capabilities deliberately left out until evidence makes them worth operating.