Thoth (tht) è il prodotto, PSD è il cliente. Nessun riferimento al contesto
clinico nel codice.
Rinomine:
- comando+package nsp→tht (dir nsp/→tht/, 46 import, pyproject entry point)
- gate nsp-gate.js→tht-gate.js (+ rewrite token, relayIfNspFails→relayIfThtFails)
- workspace chirone.{example,test}.yaml→tht.{example,test}.yaml (generici)
- env THOTH_→THT_ (19 var) + NSP_ stragglers (NSP_HARNESS_ROOT, NSP_SESSION)
- commenti/docstring chirone/psdwp3/policlinico neutralizzati ('the reference
implementation', 'the DWH')
Aggiunto [tool.setuptools.packages.find] include=['tht*'] (necessario: l'auto-
discovery rompeva con tht/ + workspaces/ come top-level multipli).
.env operatore aggiornato in-place (prefissi THT_, valori preservati, gitignored).
Verifica: pytest 109 passed, npm test 14 pass, tht phase meta --json OK, zero
residui nsp/THOTH_/NSP_/chirone nel package.
63 lines
2.6 KiB
Python
63 lines
2.6 KiB
Python
"""L1: free-text interpretation -- rationale capture contract (spec D13, §4.6).
|
|
|
|
D13 instructs the model to evaluate Altro/Rifiuta/steering free text in context,
|
|
act on it, re-ask if ambiguous (instead of defaulting), and record the user's
|
|
words in the decision rationale. The actual interpretation is model behavior
|
|
enforced by the skill prose + gate, validated at L2; this test pins the RECORDING
|
|
contract the gate relies on: free text from "Altro" must round-trip into the
|
|
decision rationale and survive persistence, never silently discarded.
|
|
"""
|
|
from pathlib import Path
|
|
|
|
from tht.decisions import append_decision, list_decisions
|
|
|
|
|
|
def test_altro_freetext_recorded_in_rationale(tmp_path):
|
|
s = tmp_path / "sess"
|
|
s.mkdir()
|
|
# The gate, on receiving Altro text, appends a decision whose rationale carries
|
|
# the user's words verbatim (not discarded, not paraphrased away).
|
|
append_decision(
|
|
s,
|
|
type="table_promoted",
|
|
subject="phase:4",
|
|
detail="ablazione",
|
|
rationale="Utente (Altro): 'ablazione' va cercato anche in patologia, non solo nel flag",
|
|
)
|
|
decisions = list_decisions(s)
|
|
assert len(decisions) == 1
|
|
assert "patologia" in decisions[0].rationale # user text preserved
|
|
assert "Altro" in decisions[0].rationale
|
|
|
|
|
|
def test_freetext_survives_persistence_roundtrip(tmp_path):
|
|
s = tmp_path / "sess"
|
|
s.mkdir()
|
|
user_text = "Rifiuto: la join corretta e' su id_episodio, non id_ricovero"
|
|
append_decision(s, type="join_modified", subject="phase:4",
|
|
detail="t1.id_ricovero", rationale=user_text)
|
|
append_decision(s, type="table_excluded", subject="phase:4",
|
|
detail="dimessi", rationale="ok") # a second, unrelated decision
|
|
reloaded = list_decisions(s)
|
|
assert reloaded[0].rationale == user_text # exact preservation
|
|
assert reloaded[1].rationale == "ok"
|
|
|
|
|
|
def test_multiline_steering_text_preserved(tmp_path):
|
|
s = tmp_path / "sess"
|
|
s.mkdir()
|
|
steering = "Considera solo il 2025.\nEscludi i day-hospital.\nUsa la data di dimissione."
|
|
append_decision(s, type="concept_clarified", subject="phase:1",
|
|
detail="periodo", rationale=steering)
|
|
assert list_decisions(s)[0].rationale == steering
|
|
# the multi-line steering is intact, including newlines
|
|
assert "\n" in list_decisions(s)[0].rationale
|
|
|
|
|
|
def test_empty_rationale_is_allowed(tmp_path):
|
|
# a decision can legitimately have no free text (e.g. a clean promote).
|
|
s = tmp_path / "sess"
|
|
s.mkdir()
|
|
append_decision(s, type="table_promoted", subject="phase:4", detail="pazienti")
|
|
assert list_decisions(s)[0].rationale == ""
|