Digvijay Singh
AI & Growth StrategyMadrid --:--
Work with me ES

Licita

An AI copilot for public-hospital procurement

Role
Product, data and AI engineering
Year
2026
Built with
  • Python
  • DuckDB
  • Mistral (API or self-hosted)
  • Streamlit
  • Docker
  • CI evals
Links

Overview

An independent research project that gives a hospital procurement team competition evidence at the moment it matters: before a tender is published.

The problem

In public procurement, most of the competition for a contract is decided before the tender is even published, by how it is drafted. A tender that attracts a single bidder costs far more than one that attracts several.

Yet procurement teams draft under deadline pressure, without evidence of how comparable hospitals buy the same thing, or which clauses have been struck down before.

The approach

  • Discovery first: an interview guide for procurement teams, premises challenged before building, and every scope decision written down.
  • A versioned data spine built from national open procurement archives, with coverage gates that fail the build if key fields degrade.
  • Language models read tribunal rulings into a strict schema, with verbatim-quote checks. Models never compute a number the reader sees.
  • An evaluation harness that scores the extraction against each tribunal’s own published outcome, with confidence intervals and dated prices. An expert-labelled set, which the clause check depends on, is the next step.
  • Anonymity by design: no buyer is named in the code, and a check in CI enforces it.

What I built

For every contract due to be re-tendered, Licita writes a memo in Spanish or English: when to start, how comparable hospitals tender the same thing, what one more bidder is worth and which suppliers to invite.

Next is the clause check: flagging clauses that resemble ones a tribunal has annulled, quoted word for word.

Status: the first live evaluation ran on three sizes of hosted model. The same code path runs on self-hosted open weights, which have not been benchmarked yet.

Why it matters

It works through the forward-deployment lifecycle on one problem: discovery, data, security, evals and a deployment that a hospital could run itself. There is no customer yet; it is an independent research project. The same pipeline runs on an API or self-hosted, so nothing has to leave the hospital’s network.

Three calls that shaped this project, and why I made them.

  1. Models never compute a number the reader sees

    Context
    The memo carries prices and rates that a procurement team will act on. A figure a language model writes from memory cannot be audited.
    Decision
    Every figure comes from SQL views and deterministic fact sheets, with its source and data date; models only read text and extract structure.
    Why
    Numbers became testable against hand-computed cases, and guardrails can hold back a misleading one. The model’s job got narrower and measurable, which is also what makes a small model viable.
  2. One code path, hosted or on premises

    Context
    Public buyers ask where their data goes and whether the model can run inside their own network.
    Decision
    Mistral is the only hosted provider, called over plain REST, and the same requests go to Mistral’s open weights on a self-hosted server by changing one setting.
    Why
    Hosted and on-premises deployments share one code path, so a pilot can move inside the customer’s network without a rewrite. I gave up built-in comparison across vendors, because the comparison buyers care about is hosted versus their own servers.
  3. Anonymity is enforced in CI, not by memory

    Context
    The first build named specific buyers and a region in code, tests and docs, some of it buried inside filters and SQL.
    Decision
    Everything buyer- or region-specific moved into a gitignored deployment profile, and a CI gate checks every tracked file against a denylist of keyed hashes.
    Why
    A rule people have to remember eventually fails; a gate fails the build instead. The hashes are keyed because an audit before publication showed plain hashes could be reversed with a short list of place names.
Read the code on GitHub