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>
70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
"""`POST /v1/process` — appelé par `apps/api` (`IntentServiceClient.process`)
|
|
en remplacement direct de l'ancien `NlpManager.process(locale, text)`. Voir
|
|
`LocalePipeline.process` pour la sémantique exacte (locale non entraînée ou
|
|
`text` vide -> résultat vide, jamais une erreur).
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends
|
|
|
|
from ..pipeline_registry import registry
|
|
from ..schemas import EntityPayload, ProcessRequest, ProcessResponse, TemperaturePayload
|
|
from ..security import require_valid_secret
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(dependencies=[Depends(require_valid_secret)])
|
|
|
|
|
|
@router.post("/v1/process", response_model=ProcessResponse)
|
|
def process(request: ProcessRequest) -> ProcessResponse:
|
|
result = registry.process(request.locale, request.text)
|
|
|
|
# Une ligne par appel — input (`locale`/`text`) et output (`entities`/
|
|
# `intent`/`score`) réunis dans la même ligne JSON, pour pouvoir suivre
|
|
# exactement ce que le pipeline a décidé pour un texte donné (voir
|
|
# `logging_config.py` pour le format).
|
|
logger.info(
|
|
"tech-step NLP process",
|
|
extra={
|
|
"locale": request.locale,
|
|
"text": request.text,
|
|
"entities": [
|
|
{"uid": entity.uid, "start": entity.start, "end": entity.end, "kind": entity.kind}
|
|
for entity in result.entities
|
|
],
|
|
"temperatures": [
|
|
{
|
|
"start": temp.start,
|
|
"end": temp.end,
|
|
"celsius": temp.celsius,
|
|
"gas_mark": temp.gas_mark,
|
|
"qualitative": temp.qualitative,
|
|
}
|
|
for temp in result.temperatures
|
|
],
|
|
"intent": result.intent,
|
|
"score": result.score,
|
|
},
|
|
)
|
|
|
|
return ProcessResponse(
|
|
entities=[
|
|
EntityPayload(uid=entity.uid, start=entity.start, end=entity.end, kind=entity.kind)
|
|
for entity in result.entities
|
|
],
|
|
temperatures=[
|
|
TemperaturePayload(
|
|
start=temp.start,
|
|
end=temp.end,
|
|
celsius=temp.celsius,
|
|
gas_mark=temp.gas_mark,
|
|
qualitative=temp.qualitative,
|
|
raw=temp.raw,
|
|
)
|
|
for temp in result.temperatures
|
|
],
|
|
intent=result.intent,
|
|
score=result.score,
|
|
)
|