3e type de metadonnee de clause, a cote des ingredients et des ustensiles : temperature en °C (« 180°C », « 200 degres », °F converti), numero de thermostat (« th. 6 ») et intensite qualitative (« feu doux/moyen/vif » -> low/medium/high). services/tech-step-intent-service : - `temperature_extraction.py` : `extract_temperatures(text, locale)` pur, a base de regex (independant du modele spaCy et de l'entrainement). - `ProcessResult` / `ProcessResponse` gagnent `temperatures` (ajout additif au contrat ; cle `gas_mark` en snake_case sur le fil). `process()` les extrait meme pour une locale pas encore entrainee. - Tests `test_temperature_extraction.py` (10) ; `test_routes_process.py` : 2 assertions d'egalite stricte gagnent `"temperatures": []`, +1 cas. apps/api : - `IntentServiceTemperature` (mappe `gas_mark` -> `gasMark` a la frontiere). - `TechStepMatch.temperatures` : filtrees par appartenance de span a la clause, meme regle que les ustensiles. - `model StepTechStepTemperature` (aucune FK — la valeur structuree EST la donnee) + migration manuelle `20260829120000_step_tech_step_temperature` (cascade via le TRUNCATE de `step_tech_step`, rien a ajouter a reset-db.ts). Persistance + lecture dans `recipe.service.ts` (`recipeInclude`, `toStepTechStepViews`) et l'include de sequence fraiche du service de correction ; `sources.service.ts` (apercu d'import) les fait transiter. packages/shared : `StepTechStepTemperatureView` + `temperatures` sur `StepTechStepView`. apps/web : - `splitDescriptionSegments` enrobe `splitDescriptionByTechSteps` et redecoupe les segments non-keyword autour des spans de temperature (la logique technique intriquee reste intacte). `?? []` tolere une payload d'avant `temperatures`. - `StepDescription` : surlignage `.step-temperature` + Tooltip via `temperatureLabel` ; i18n `recipes.temperature.*`. - Tests composants +4 (22 verts) ; pas d'UI de correction (v1, comme les ustensiles). Verifie : biome + tsc + `pnpm -r build` ; web 102/103 e2e (l'echec est le flake pre-existant recipe-form.feature, sans rapport) + 49/49 composants ; pytest temperature + routes 15/15. `pnpm --filter api test` (Postgres + intent-service requis) non lance ici. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
"""Modèles Pydantic du contrat HTTP — voir le plan de migration pour le
|
|
contrat exact attendu côté `apps/api` (`IntentServiceClient`,
|
|
`apps/api/src/lib/recipe-matching/intent-service-client.ts`).
|
|
|
|
Pas de `POST /v1/train` ici — ce service s'entraîne lui-même au démarrage
|
|
depuis `training_data.py` (voir `pipeline_registry.py`/`main.py`), plus
|
|
besoin d'un contrat HTTP pour ça.
|
|
"""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# POST /v1/process
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class ProcessRequest(BaseModel):
|
|
locale: str
|
|
text: str
|
|
|
|
|
|
class EntityPayload(BaseModel):
|
|
"""Une mention candidate — technique ou ustensile, voir `kind` — offsets
|
|
caractère `[start, end)` dans `text`, convention identique à
|
|
`String.prototype.slice` côté `apps/api` (pas de décalage `+1` à
|
|
appliquer côté Node, contrairement à l'ancien `NlpManager` de
|
|
node-nlp).
|
|
|
|
`kind` distingue de quel `PhraseMatcher` la mention vient (voir
|
|
`locale_pipeline.py`'s `Entity`) — `apps/api`'s `tech-step-matcher.ts`
|
|
en a besoin pour savoir laquelle des deux résoudre (`TechStep.key` vs
|
|
`Utensil.key`)."""
|
|
|
|
uid: str
|
|
start: int
|
|
end: int
|
|
kind: Literal["technique", "utensil"] = "technique"
|
|
|
|
|
|
class TemperaturePayload(BaseModel):
|
|
"""Une température reconnue dans `text` — voir
|
|
`temperature_extraction.Temperature`. Ajout additif au contrat (les
|
|
clients plus anciens l'ignorent) : liste `temperatures` à part des
|
|
`entities`, car une température porte des champs structurés
|
|
(`celsius` / `gas_mark` / `qualitative`) qui ne rentrent pas dans le
|
|
`uid` d'un `EntityPayload`. Au moins un des trois est non nul."""
|
|
|
|
start: int
|
|
end: int
|
|
celsius: float | None = None
|
|
gas_mark: int | None = None
|
|
qualitative: Literal["low", "medium", "high"] | None = None
|
|
raw: str
|
|
|
|
|
|
class ProcessResponse(BaseModel):
|
|
entities: list[EntityPayload]
|
|
intent: str | None
|
|
score: float
|
|
temperatures: list[TemperaturePayload] = []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /health
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class HealthResponse(BaseModel):
|
|
status: str
|