1"""Provenance is mandatory, and survives a round trip.
3Phase 02's headline exit criterion is that *a parameter with no source is a
4validation error rather than a default*. That is what these tests hold. The rest
5check that the record survives serialisation intact, because provenance that is
6lost on the first save is provenance that does not exist.
9from __future__
import annotations
26 """The criterion, stated directly.
28 A missing source must not default to anything -- not to ESTIMATED, not to
29 DEFAULT. Defaulting it would mean every un-sourced number silently acquired
30 a plausible-looking provenance, which is worse than having none at all.
32 with pytest.raises(ValueError, match=
"missing required field"):
33 Param.from_dict({
"value": 1.2,
"unit":
"Cv"}, where=
"valve.Cv")
37 with pytest.raises(ValueError, match=
"not a valid source"):
39 {
"value": 1.0,
"unit":
"m",
"source":
"vibes"}, where=
"pipe.bore"
44 """Constructed directly, a stringly-typed source is a type error."""
45 with pytest.raises(TypeError, match=
"must be a Provenance"):
46 Param(1.0,
"m",
"manufacturer")
50 with pytest.raises(UnknownUnit):
51 Param(1.0,
"smoots", Provenance.ESTIMATED)
55 """Round-trip fidelity depends on keeping the authored form.
57 Converting on construction would mean a config written in psi came back as
58 a float with a trail of decimals -- a diff on every save, and a file that no
59 longer looks like what someone wrote.
61 p =
Param(500.0,
"psi", Provenance.MANUFACTURER,
"MEOP")
62 assert p.value == 500.0
63 assert p.unit ==
"psi"
64 assert p.si == pytest.approx(3447378.6, rel=1e-6)
65 assert p.dimension ==
"pressure"
69 """A parameter survives the full save/load path unchanged."""
73 source=Provenance.MANUFACTURER,
74 reference=
"SS-8BK-V51 datasheet rev C, table 2",
77 restored = Param.from_dict(json.loads(json.dumps(original.to_dict())))
78 assert restored == original
82 """A terse parameter stays terse when written back."""
83 data =
Param(1.0,
"m", Provenance.DEFAULT).to_dict()
84 assert set(data) == {
"value",
"unit",
"source"}
89 assert relative.bounds(100.0) == pytest.approx((90.0, 110.0))
92 assert absolute.bounds(100.0) == pytest.approx((95.0, 105.0))
95 assert relative.bounds(-100.0) == pytest.approx((-110.0, -90.0))
98@pytest.mark.parametrize("kind", ["percent", "", "Relative"])
100 with pytest.raises(ValueError, match=
"relative|absolute"):
105 with pytest.raises(ValueError, match=
"non-negative"):
110 p =
Param(100.0,
"psi", Provenance.MEASURED,
"PT-04",
Uncertainty(
"relative", 0.01))
111 low, high = p.si_bounds()
or (0.0, 0.0)
112 assert low == pytest.approx(p.si * 0.99)
113 assert high == pytest.approx(p.si * 1.01)
114 assert Param(1.0,
"m", Provenance.DEFAULT).si_bounds()
is None
118 assert Provenance.MEASURED.rank > Provenance.MANUFACTURER.rank
119 assert Provenance.MANUFACTURER.rank > Provenance.ESTIMATED.rank
120 assert Provenance.ESTIMATED.rank > Provenance.DEFAULT.rank
124 assert Provenance.ESTIMATED.is_assumed
125 assert Provenance.DEFAULT.is_assumed
126 assert not Provenance.MEASURED.is_assumed
127 assert not Provenance.MANUFACTURER.is_assumed
131 """The point of carrying provenance at all.
133 "Nine measured, four datasheet, two guesses" is a different claim from a
134 bare number, and the difference should never be invisible in a design
138 "a":
Param(1.0,
"m", Provenance.MEASURED,
"CF-2026-03"),
139 "b":
Param(2.0,
"m", Provenance.MANUFACTURER,
"datasheet"),
140 "c":
Param(3.0,
"m", Provenance.ESTIMATED,
"similar part"),
141 "d":
Param(4.0,
"m", Provenance.DEFAULT,
"library default"),
143 assert provenance_summary(params) == {
149 assert assumed_params(params) == [
"c",
"d"]
153 """A stable table, so a report does not gain and lose columns."""
154 assert set(provenance_summary({})) == {p.value
for p
in Provenance}
None test_a_parameter_with_no_source_is_rejected()
None test_value_is_stored_as_authored_and_converted_on_demand()
None test_an_unknown_unit_is_caught_at_construction()
None test_uncertainty_bounds()
None test_round_trip_through_json_is_exact()
None test_a_report_can_say_what_a_result_rests_on()
None test_provenance_is_ranked_so_measurements_win()
None test_summary_always_reports_every_kind()
None test_the_source_must_be_the_enum_not_a_string()
None test_a_source_must_be_one_of_the_declared_kinds()
None test_optional_fields_are_omitted_rather_than_nulled()
None test_si_bounds_are_in_canonical_units()
None test_uncertainty_cannot_be_negative()
None test_assumed_flags_the_values_nobody_established()
None test_uncertainty_kind_is_checked(str kind)