Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/flow/openadapt_flow/provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,24 @@ def bound_fields(effects: Any) -> dict[str, str]:
return out


def bound_maps(effects: Any) -> dict[str, dict[str, str]]:
"""Parameter name -> the value map its field declares (label -> code)."""

out: dict[str, dict[str, str]] = {}
for effect in effects:
value = getattr(effect, "value", None)
if value is not None and value.param and value.map:
out.setdefault(value.param, dict(value.map))
return out


def round_trip(
provenance: ProvenanceFile,
record: Optional[Mapping[str, Any]],
fields: Mapping[str, str],
documents: Mapping[str, Any],
second: Extractor,
maps: Optional[Mapping[str, Mapping[str, str]]] = None,
) -> RoundTrip:
"""Compare ``record`` (as read back) with the source of each value.

Expand Down Expand Up @@ -398,6 +410,11 @@ def round_trip(
checks.append(FieldCheck(name, "no_document"))
continue
source_value = second.read(document, name, prov.source)
mapping = (maps or {}).get(name)
if mapping is not None and source_value is not None:
# The document shows the label, the record stores its code (a
# state's name and "MA", a provider's name and their id).
source_value = mapping.get(source_value.strip())
stored = "" if record.get(record_field) is None else str(record[record_field])
if source_value is None:
checks.append(FieldCheck(name, "missing", stored))
Expand Down
10 changes: 9 additions & 1 deletion packages/flow/openadapt_flow/verify_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ def _judge_source(

from openadapt_flow.provenance import (
bound_fields,
bound_maps,
default_extractor,
round_trip,
)
Expand All @@ -542,7 +543,14 @@ def _judge_source(
}
extractor = second or default_extractor(provenance)
record = _target_record(effects, verdicts) if delta_held else None
result = round_trip(provenance, record, bound_fields(effects), documents, extractor)
result = round_trip(
provenance,
record,
bound_fields(effects),
documents,
extractor,
maps=bound_maps(effects),
)
evidence = result.summary()
evidence["first_extractors"] = sorted(
{p.extractor for p in provenance.params.values()}
Expand Down
48 changes: 48 additions & 0 deletions packages/flow/tests/test_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,51 @@ def test_a_task_whose_values_differ_from_its_provenance_is_refused(
with pytest.raises(GuardRefused) as refused:
load_task(path)
assert refused.value.code == "task_invalid"


def test_round_trip_maps_a_label_to_the_stored_code() -> None:
"""The referral prints the state's name; the record stores its code."""

from openadapt_flow.provenance import bound_fields, bound_maps

document = IntakeDocument(
"r.txt", "text", ["Last name: Okafor", "State: Massachusetts"]
)
sha = sha256_bytes(document.text.encode("utf-8"))
prov = ProvenanceFile(
params={
name: ParamProvenance(
value=value,
source=SourceRef(kind="document", sha256=sha, line=line, label=label),
extractor="model:first-reader",
)
for name, value, line, label in (
("lname", "Okafor", 1, "Last name"),
("state_label", "Massachusetts", 2, "State"),
)
}
)
match = {"lname": {"param": "lname"}}
effects = [
Effect(kind=EffectKind.RECORD_WRITTEN, match=match),
Effect(
kind=EffectKind.FIELD_EQUALS,
match=match,
field="state",
value={"param": "state_label", "map": {"Massachusetts": "MA"}},
),
]
from openadapt_flow.provenance import default_extractor

def judge(stored: str) -> str:
return round_trip(
prov,
{"id": 1, "lname": "Okafor", "state": stored},
bound_fields(effects),
{sha: document},
default_extractor(prov),
maps=bound_maps(effects),
).status

assert judge("MA") == "held"
assert judge("RI") == "refuted"
Loading