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>
82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
"""`temperature_extraction.extract_temperatures` — regex pure, sans modèle
|
|
ni entraînement (voir le module)."""
|
|
|
|
from intent_service.temperature_extraction import extract_temperatures
|
|
|
|
|
|
def test_no_temperature_returns_empty_list():
|
|
assert extract_temperatures("Émincer les oignons finement", "fr") == []
|
|
assert extract_temperatures(" ", "fr") == []
|
|
|
|
|
|
def test_celsius_with_degree_sign_and_c():
|
|
[temp] = extract_temperatures("Cuire à 180°C pendant 30 min", "fr")
|
|
assert temp.celsius == 180.0
|
|
assert temp.gas_mark is None
|
|
assert temp.qualitative is None
|
|
assert temp.raw == "180°C"
|
|
assert (temp.start, temp.end) == (8, 13)
|
|
|
|
|
|
def test_celsius_spelled_out_degrees():
|
|
[temp] = extract_temperatures("Préchauffer le four à 200 degrés", "fr")
|
|
assert temp.celsius == 200.0
|
|
assert temp.raw == "200 degrés"
|
|
|
|
|
|
def test_bare_degree_sign_reads_as_celsius():
|
|
[temp] = extract_temperatures("Monter le four à 240°", "fr")
|
|
assert temp.celsius == 240.0
|
|
|
|
|
|
def test_fahrenheit_is_converted_and_rounded():
|
|
[temp] = extract_temperatures("Bake at 350°F", "en")
|
|
assert temp.celsius == 177.0 # round((350 - 32) * 5 / 9)
|
|
assert temp.raw == "350°F"
|
|
|
|
|
|
def test_thermostat_and_gas_mark():
|
|
[fr] = extract_temperatures("Enfourner à thermostat 6", "fr")
|
|
assert fr.gas_mark == 6
|
|
assert fr.celsius is None
|
|
|
|
[short] = extract_temperatures("Cuisson à th. 7", "fr")
|
|
assert short.gas_mark == 7
|
|
|
|
[en] = extract_temperatures("Cook at gas mark 4", "en")
|
|
assert en.gas_mark == 4
|
|
|
|
|
|
def test_qualitative_heat_fr():
|
|
levels = {
|
|
extract_temperatures(f"Faire revenir à feu {word}", "fr")[0].qualitative
|
|
for word in ("doux", "moyen", "vif", "fort")
|
|
}
|
|
assert levels == {"low", "medium", "high"}
|
|
|
|
[temp] = extract_temperatures("Laisser mijoter à feu doux", "fr")
|
|
assert temp.qualitative == "low"
|
|
assert temp.celsius is None and temp.gas_mark is None
|
|
assert temp.raw == "feu doux"
|
|
|
|
|
|
def test_qualitative_heat_en():
|
|
[temp] = extract_temperatures("Sear over high heat", "en")
|
|
assert temp.qualitative == "high"
|
|
assert temp.raw == "high heat"
|
|
|
|
|
|
def test_multiple_temperatures_in_order_without_overlap():
|
|
temps = extract_temperatures("Saisir à feu vif puis enfourner à 180°C, finir à th. 4", "fr")
|
|
assert [(t.qualitative, t.celsius, t.gas_mark) for t in temps] == [
|
|
("high", None, None),
|
|
(None, 180.0, None),
|
|
(None, None, 4),
|
|
]
|
|
assert [t.start for t in temps] == sorted(t.start for t in temps)
|
|
|
|
|
|
def test_fahrenheit_not_double_counted_as_celsius():
|
|
temps = extract_temperatures("Bake at 400°F", "en")
|
|
assert len(temps) == 1
|
|
assert temps[0].celsius == 204.0
|