Work · Real-estate investment / pension-fund-linked portfolio (GCC)

Portfolio Performance Monitoring Agent.

RoleSole builder - schema inference, agent design, dashboard, deployment
Period2026 - Q2
StatusDemo delivered and debriefed positively with client; follow-on scoping
Scale~2,500 LOC Python backend · 5 tools + peer-benchmark helper · React SPA · Docker-deployed
PythonFastAPIAnthropic tool-use looppandasnumpyReactViteDockerGunicorn/Uvicorn

Portfolio Performance Monitoring Agent

Natural-language performance monitoring over a ~175-asset real-estate portfolio, built by reverse-engineering the schema out of a consultant’s working spreadsheet - with client figures never leaving the room.


1. The problem

An institutional investor’s portfolio baseline existed where these things always exist: a large, organically-grown Excel workbook. Thirteen sheets, roughly 175 asset rows, with calculation sheets, chart-source sheets, dividend tables, financial breakdowns, total-shareholder-return calculations in multiple fee variants, and lookup tables - all cross-linked, none documented.

The ask was to demonstrate how AI capability could support portfolio performance monitoring. The real engineering problem was upstream of any dashboard: you cannot build against a schema nobody has written down.

Two constraints made it harder:

  • The time dimension wasn’t a column. There was no snapshot date field - the temporal axis was encoded in year-suffixed KPI column names. Any naive tabular ingestion would silently flatten years together.
  • Confidentiality. This was live client financial data. The schema had to be extracted and documented while numerical values were deliberately excluded - structure captured, figures left behind.

2. Approach

Phase 1 - Privacy-constrained schema inference

Rather than hand-auditing thirteen sheets, I built a structured extraction prompt run against the workbook and the accompanying assessment deck, producing a formal schema specification: sheet-by-sheet role classification (data table / calculation / chart source / lookup / blank), primary-key identification, and a column-by-column inventory capturing inferred type, semantic role, percentage missing, distinct-value bucket, example labels, and a per-column confidence score.

The privacy rule was encoded into the extraction itself: asset names permitted as structural evidence, numerical values excluded by instruction, with sensitive categoricals explicitly marked redacted. The output was a reviewable specification document - reviewed and annotated with decisions before a line of application code was written.

Confidence scoring per column mattered in practice: it told me exactly which fields to verify with the engagement team rather than treating every inference as equally reliable.

Phase 2 - Agent implementation

The specification translated directly into agent configuration: typed domain model, the client’s own assessment framework pinned into the system prompt so the agent reasons in the client’s vocabulary, and tool-based data access.

The agent answers only through tools - never from model memory. A real tool-use loop iterates against the model API, dispatching tool calls through a registry and feeding results back, with a fallback to a cheaper model tier if the primary is unavailable. Five tools: schema inspection (column inventory, section grouping, sample values - designed to be called first), single-column aggregation (sum/avg/min/max/median/count with a filter expression language), group-by aggregation, structured query (select/where/order/limit), and asset lookup by ID or name.

Schema and distinct values are fetched before any filter is applied, which prevents the classic failure of an LLM confidently filtering on a category value that doesn’t exist. All aggregation is computed in code - the model chooses arguments and narrates results; it never does arithmetic.

Two details I’d point to as the real work. The filter layer implements a genuine operator language (equals, contains, greater/less-than, in, is-empty) with duplicate-column deduplication, because the source workbook is a messy real-world export. And the drill-down helper computes peer benchmarking properly: peer averages by investment strategy, percentile rank within the cohort, and equal-width histogram binning - so an asset is positioned against its peers rather than just displayed.

The tool descriptions also carry corrective guidance steering the model toward the right column among several similarly-named ones, including past a misspelled column heading in the client’s own workbook. Rather than silently renaming client columns (which would break every future re-import), the fix lives in the tool contract. Column typos are preserved deliberately: the workbook is the client’s, and the application stays agnostic to its quirks.

Phase 3 - Dashboard and delivery

A dashboard surface over the same data - summary statistics, status distribution, category and strategy breakdowns, asset-level drill-down - deployed to cloud static hosting with enterprise identity.


The privacy control that actually shipped

The runtime configuration hard-codes a synthetic workbook, with the real baseline file excluded from the container build. The schema tool itself reports the position honestly to the model: asset names are real; numeric values are synthetic.

So the demo shows genuine portfolio structure and real asset names with shuffled figures, and the client’s actual financial values were never deployed anywhere. That is the correct shape for a capability demo on live client data - and it’s enforced by build exclusion and configuration, not by remembering to swap a file.

3. What I’d highlight

  • Recognising schema inference as the actual first problem. The instinct is to start building the dashboard; the correct move was to formally reverse-engineer and document the data contract first, with confidence scores flagging what needed human verification.
  • Privacy encoded into the extraction step, not applied as a cleanup pass afterwards - structure extracted, client figures never captured.
  • Catching the temporal encoding. Years living in column names rather than a date column is exactly the trap that silently corrupts every subsequent calculation.
  • Tools-only agent grounding, with schema and distinct values fetched before filtering - eliminating hallucinated filter values by construction.
  • Client vocabulary in the system prompt, so answers land in the client’s own framework rather than generic financial language.
  • Reusable pattern. This build became the template for a subsequent government strategy-monitoring dashboard in an entirely different domain - the infrastructure and interaction model transferred; the domain logic was rebuilt.

4. Skills demonstrated

schema inference from unstructured spreadsheets · privacy-by-design data extraction · LLM tool-calling architecture · hallucination prevention via tool grounding · structured query agents · domain-framework prompt engineering · FastAPI · React dashboard development · Azure Static Web Apps · rapid client-facing prototyping

The first step

Twenty minutes. You describe where AI is stuck.

You leave the call knowing whether I can help, roughly what it would take, and what it would cost to find out for sure. If I am not the right person, I will say so on the call.

The 20 minutes, in order

  1. You talk first. Where AI is stuck, what has been tried, what it costs today.
  2. I answer plainly. Whether I can help, and what I would look at first.
  3. You leave with a next step. An audit scope, a pointer elsewhere, or a clean no.

Before you book

  1. Your stack is not too messy to start. Messy is the normal starting condition.
  2. Training that does not survive the week is the normal outcome. These sessions build on your backlog and ship something real.
  3. You do not need budget approved to take the call. You need it approved to start step two.