Context Compiler

A Huwise / Opendatasoft data portal compiled into tested context for AI agents. Development portal: data.education.gouv.fr.

A portal already gives agents an API for every dataset. What agents lack is context: which of five similar datasets is the right one, what a field means, how its values are written. This project compiles the portal's catalog into one short card per dataset, serves the cards over MCP with search boosted by real usage, and measures with a golden set whether the cards make an agent answer better.

314datasets
6976fields
314cards
3MCP tools

Try it

Live, read-only MCP server (streamable HTTP): https://context.smiro.dev/mcp. Tools: find_dataset, explain_dataset, query (ODSQL is checked against the real schema before it reaches the portal).

claude mcp add --transport http context https://context.smiro.dev/mcp --header "X-Domain: data.education.gouv.fr"

Then ask: « Combien de collèges publics en Côte-d'Or ? » → 58.

For agents without MCP: llms.txt and 314 pages in datasets/. Source: github.com/SergeMiro/context-compiler-huwise.

Traps an agent falls into

Writing 30 reference queries by hand failed 11 times on the first run, on exactly these:

trapexample
one department, three formatsCôte-d'Or is '021' in the directory, '21' in IPS datasets, "COTE D'OR" as a label
years stored as datessession='2025' → API type error; year(session)=2025 works
numbers stored as textavg(ips) fails on IPS écoles; order_by ips_etab desc sorts as strings
case differsacademie='DIJON' vs libelle_academie='Dijon'

Cards carry real sample values for key fields and a hint for every date field, so the agent reads the format instead of guessing it.

Usage signal

The portal publishes the log of its own MCP server (39 675 agent calls, Feb–Nov 2026). It is used as a search signal: plain BM25 does not put the directory in the top 5 for « combien d'écoles en France »; with the signal it is #1.

most used by agentscalls
fr-en-annuaire-education
Annuaire de l'éducation
3389
fr-en-adresse-et-geolocalisation-etablissements-premier-et-second-degre
Adresse et géolocalisation des établissements d'enseignement des premier et second degrés
2186
fr-en-baccalaureat-par-academie
Le baccalauréat par académie
1006
fr-en-ecoles-effectifs-nb_classes
Effectifs d’élèves par école
1001
fr-en-indicateurs-de-resultat-des-lycees-gt_v2
Indicateurs de valeur ajoutée des lycées d'enseignement général et technologique
815

Does it help? Same agent, two toolsets

No full eval run yet: manage.py eval data.education.gouv.fr --mode both.

One card

Short card (≤ 400 tokens) returned by find_dataset, version 1.

## fr-en-annuaire-education — Annuaire de l'éducation
Annuaire de l'éducation : données sur les établissements publics et privés ouverts situés en France. Le jeu de données couvre le premier degré, le second degré, les établissements médico-sociaux, les Centre d'Information et d'Orientation ainsi que les établissements administratifs.
68563 rows · updated 09.11.2026 · Licence Ouverte v2.0 (Etalab) · DNE - Ministère de l'Education Nationale
Keys: identifiant_de_l_etablissement, code_postal (e.g. '02100'), code_commune, code_departement (e.g. '059'), code_academie, code_region, siren_siret
Fields: nom_etablissement, type_etablissement, statut_public_prive, nom_commune, ecole_maternelle, ecole_elementaire, voie_generale, voie_technologique, voie_professionnelle, web, restauration, hebergement, … +52

One card edit

The improve loop proposes one edit per failing dataset; it is kept only if failing questions get fixed and no passing one breaks. A human reviews the diff in Django admin.

fr-en-annuaire-education · note · human · proposed

## fr-en-annuaire-education — Annuaire de l'éducation
Annuaire de l'éducation : données sur les établissements publics et privés ouverts situés en France. Le jeu de données couvre le premier degré, le second degré, les établissements médico-sociaux, les Centre d'Information et d'Orientation ainsi que les établissements administratifs. Sources : données ONISEP et RAMSESE
68563 rows · updated 09.11.2026 · Licence Ouverte v2.0 (Etalab) · DNE - Ministère de l'Education Nationale
Themes: Données de référence, structures, unités d'enseignement, établissements, écoles, EPLE, public, privé, internat, demi-pension, restauration, cité scolaire, CFA, lycées, collèges, lycées professionnels
### Fields
- identifiant_de_l_etablissement · text · key · numéro de l'unité administrative immatriculée (uai)
- code_postal · text · key · code postal · e.g. '02100', '01000', '02200', '01100', '02000'
- code_commune · text · key · code insee de la commune
- code_departement · text · key · code du département · e.g. '059', '013', '062', '069', '077'
- code_academie · text · key · code de l'académie
- code_region · text · key · Code_region
- siren_siret · text · key · numéro SIRET de l'établissement (constitué du numéro SIREN sur 9 chiffres et du numéro NIC sur 5 chiffres)
- nom_etablissement · text · nom de l'établissement
- type_etablissement · text · type regroupé d'établissement : école, collège, lycée, EREA, ... · e.g. 'Ecole', 'Collège', 'Lycée', 'Médico-social', 'Service Administratif'
- statut_public_prive · text · Statut public ou privé, si applicable · e.g. 'Public', 'Privé'
- nom_commune · text · nom de la commune · e.g. 'Agen', 'Abbeville', 'Agde', 'Achères', 'Acoua'
- ecole_maternelle · int · indique si l'établissement est une école maternelle · e.g. '1', '0'
- ecole_elementaire · int · indique si l'établissement est une école élémentaire · e.g. '1', '0'
- voie_generale · text · indique si l'établissement dispense les enseignements de la voie générale · e.g. '0', '1'
- voie_technologique · text · indique si l'établissement dispense les enseignements de la voie technologique · e.g. '0', '1'
- voie_professionnelle · text · indique si l'établissement dispense les enseignements de la voie professionnelle · e.g. '0', '1'
- web · text · site web
- restauration · int · indique si l'établissement dispose d'un service de restauration · e.g. '1', '0'
- hebergement · int · indique si l'établissement dispose d'un service d'hébergement · e.g. '0', '1'
- ulis · int · indique si l'établissement dispose d'une unité localisée pour l'inclusion scolaire · e.g. '0', '1'
- apprentissage · text · indique si l'établissement propose des formations par apprentissage · e.g. '0', '1'
- segpa · text · indique si l'établissement dispose d'une "section d'enseignement général et professionnel adapté" · e.g. '0', '1'
- section_arts · text · indique si l'établissement possède une section arts · e.g. '0', '1'
- section_cinema · text · indique si l'établissement possède une section cinéma · e.g. '0', '1'
- section_theatre · text · indique si l'établissement possède une section théâtre · e.g. '0', '1'
- section_sport · text · indique si l'établissement possède une section sport · e.g. '0', '1'
- section_internationale · text · indique si l'établissement possède une section internationale · e.g. '0', '1'
- section_europeenne · text · indique si l'établissement possède une section européenne · e.g. '0', '1'
- lycee_agricole · text · indique si l'établissement est un lycée agricole · e.g. '0', '1'
- lycee_militaire · text · indique si l'établissement est un lycée militaire · e.g. '0', '1'
- lycee_des_metiers · text · Indique si l'établissement est labellisé "lycée des métiers" · e.g. '0', '1'
- post_bac · text · Indique si l'établissement propose des formations après le baccalauréat · e.g. '0', '1'
- appartenance_education_prioritaire · text · Indique si l'établissement est situé en réseau d'éducation prioritaire (REP), ou réseau d'éducation prioritaire renforcé (REP+) · e.g. 'REP', 'REP+'
- greta · text · indique si l'établissement fait partie d'un GRETA (groupement d'établissements) · e.g. '0', '1'
- fiche_onisep · text · url de la fiche onisep
- type_contrat_prive · text · dans le cas d'un établissement privé, libellé du type de contrat
- libelle_departement · text · nom du département · e.g. 'Nord', 'Bouches-du-Rhône', 'Pas-de-Calais', 'Rhône', 'Seine-et-Marne'
- libelle_academie · text · nom de l'académie · e.g. 'Versailles', 'Lille', 'Créteil', 'Nantes', 'Bordeaux'
- libelle_region · text · nom de la région · e.g. 'Ile-de-France', 'Auvergne-Rhône-Alpes', 'Hauts-de-France', 'Nouvelle-Aquitaine', 'Occitanie'
- coordx_origine · double · première coordonnée géographique de l'établissement dans le système de coordonnées légal
- coordy_origine · double · deuxième coordonnée géographique de l'établissement dans le système de coordonnées légal
- epsg_origine · text · code EPSG du système de coordonnées légal
- nom_circonscription · text · nom de la circonscription · e.g. 'Pôle Ecole Inclusive', 'Circonscription d'inspection du 1er degr', 'Circonscription 1er degré IEN Ecoles Pri', 'Circonscription d'inspection du 1er degr', 'Circonscription d'inspection du 1er degr'
- latitude · double · latitude WGS84  de l'établissement
- longitude · double · longitude WGS84  de l'établissement
- precision_localisation · text · précision de la localisation géographique
Valeurs possibles
1/ l'adresse ponctuelle a été vérifiée par un collecteur:
1. BÂTIMENT
2/ l'adresse ponctuelle corres
- date_ouverture · date · date d'ouverture de l'établissement · filter by year: year(date_ouverture)=2025
- date_maj_ligne · date · date de mise à jour des informations · filter by year: year(date_maj_ligne)=2025
- … 23 more fields not shown
### Limits
- records: limit <= 100 per call; to count rows use limit=0 and read total_count; string literals in single quotes; codes are text: keep leading zeros.
code_departement a 3 chiffres : Côte-d'Or = '021', Nord = '059' (les jeux IPS utilisent code_du_departement = '21')

piège observé en écrivant les questions de contrôle

Architecture

COLD PATH (commands / Celery; the only place an LLM is ever called)
  Explore API --scan--> Portal / Dataset / Field   (MariaDB = single source of truth)
                          | compile (deterministic)
                          v
                        Card v1..vN  <-- kept ContextEdit
             +------------+-------------+
             v            v             v
        ES `cards`     Neo4j          llms.txt + datasets/<id>.md
  eval:    EvalCase -> agent -> EvalRun / EvalResult
  improve: failures -> LLM proposes edit -> compile -> eval -> kept | reverted

HOT PATH (MCP, no LLM, no writes except the ToolCall log)
  find_dataset(question) -> ES (filter + routing by portal) + usage boost -> short cards
  explain_dataset(id)    -> full card + joinable datasets + lineage (Neo4j)
  query(id, odsql)       -> validator -> Explore API (client key passed through, limit <= 100)

Stack, the same as Huwise: Django + DRF, MariaDB, Celery + RabbitMQ, Elasticsearch, Neo4j, MCP Python SDK. find_dataset p95 13 ms, explain_dataset p95 23 ms in process.