About

I design the architecture, build it, and publish how I measured it.

I build systems where a failure costs money or costs a fine: electronic invoicing to the Peruvian tax authority, payment gateways, accounting ledgers and reconciliation. On that base I add AI where the improvement is measurable, and I publish the conditions each figure was measured under.

I design and build systems where a failure carries fiscal or legal consequence: electronic invoicing, payment gateways, ledgers and reconciliation.

I work from Lima and lead the technical team of a nine-person agency.

Every figure on this page carries the conditions it was measured under and a link to its source.

6years of software
2years of AI in production
50+projects delivered
45+clients
20+systems in production
9people on the team
How I work

Four rules I do not negotiate

01

Measure before claiming

Every figure I publish carries the conditions it was measured under. A recall@10 without naming the gold set is not data, it is an opinion.

02

Document where it breaks

Every system has a failure point. The forecasting one is at 510 products, and it is calculated, not guessed. I would rather say it than have a client find it.

03

Leave my mistakes written down

I estimated 25 seconds and measured 39.17. I bet on gradient boosting and AR(1) won. The gap between estimating and measuring is the work.

04

Choose on cost, not fashion

I dropped CloudWatch because at that cardinality it cost 44 USD a month. Concurrency is 8 because the account quota was 10, not because 8 was optimal.

What I cannot do

I would rather be filtered out for what is missing than be caught out in a technical interview.

What I use
  • AWS
  • Python, TypeScript
  • FastAPI, Next.js, React
  • BM25 and vector search, RRF
  • Step Functions, Lambda, S3
  • DynamoDB, PostgreSQL, Prisma
  • CDK
  • pytest, vitest
  • TensorFlow, MediaPipe
Never used it
  • Azure, Google Cloud
  • Databricks
  • Snowflake
  • Microsoft Fabric
  • Unity Catalog
  • MLflow
  • OCR and document extraction
  • Go
  • Formal Spec Driven Development
Every measurement

Eight figures, each with the conditions it was taken under and the source that backs it, or the one it is still missing.

0.862What the vector half earns

Recall of the search isolated from the deterministic floor, with the real embedder. The toy embedder gave 0.759: that difference is the only thing proving the semantic half is not decoration

2Gold set violations

With the real embedder. Asking for papaya, which is not in the catalogue, returns potato records: 0.6444 against 0.6656, two hundredths apart. No threshold separates them without killing the aggregate questionsunsolved in retrieval

8MCP tools

MCP server in production for the RAG system

13FastAPI routes

The same surface, exposed over HTTP

435 + 77RAG tests

435 in pytest, 77 in vitestcoverage missing

0.1711Gradient boosting

Same walk-forward validation. Lost to AR(1)name the metric

707.5 msSnapshot in handler

Before moving it out. After: 1.78 ms

8Map concurrency

The account quota was 10

44 USDCloudWatch rejected

Per month at that cardinality. DynamoDB chosen instead

53ADM end to end

Over 543 tests and 29 domain services

Where it breaks

Limits carry the same weight as results.

RAG system

Papaya returns potato, and I did not fix it in retrieval

Asking for a product that is not in the catalogue returns the closest one by name. Papaya scores 0.6444 and potato 0.6656: two hundredths apart. I tried the obvious threshold and it does not work, because any cut that separates them kills the aggregate questions, which score 0.5151. I solved it by warning the model from the lexical filter, which already knew no word matched the catalogue. The evaluation still reports both violations: I did not relax the gold set.

Forecasting on AWS

It breaks at 510 products

514 bytes per Map element against the 256 KB Step Functions state ceiling. Past that number the Map has to be paginated or the payload moved to S3.

RAG system

recall@10 holds inside the indexed domain

The gold set is 32 cases I wrote myself over 73 products and 537 dates. It is a small, single-author sample: it catches regressions, it does not prove the system generalises.

OrquestadorADM

SUNAT between 100 and 1999 is inconclusive

There is no way to know whether the document was registered. The rule is not to reissue and to wait for the status query, because reissuing duplicates the counter.

Deployment package

The deployment package, unpublished

207.6 MB against a 250 MB limit. Held back until I confirm whether that figure is the zipped or the unzipped package, because the Lambda limit applies to the unzipped one. It gets corrected or it leaves the list, but it does not get published half-right. unverified