This notebook introduces requirement usage; after running it you can apply TimelyToast to a named element and record whether the measure it constrains is a measure of effectiveness or a measure of performance.
Chapter 2 defined TimelyToast as a requirement definition with a Toaster subject. A requirement definition describes what must hold; a requirement usage applies it. This notebook adds requirement timely : TimelyToast;, then records the judgment that decides what kind of measure timely is: a case-specific decision about who cares and whether the measure names acceptance or performance, not one read off a file name.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
# editor.add_requirement(owner='ToasterDemo', name='timely', type='TimelyToast') when API ships
TIMELY_USAGE = "requirement timely : TimelyToast;"
print(TIMELY_USAGE)
source = Path("../../models/ch03-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"requirement timely : TimelyToast;
The requirement usage loads without diagnostics. The next cell checks what happens when a requirement usage names a definition that does not exist.
# Negative control: a requirement usage referencing an undefined requirement
# definition raises "unresolved reference" at the usage site.
bad_source = """
package Bad {
private import ScalarValues::*;
requirement timely_bad : UndefinedRequirement;
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok
print("Expected error:", bad.diagnostics[0].message)Expected error: unresolved reference: UndefinedRequirement
With the negative control confirmed, the next cell looks up the requirement usage in the loaded model.
req_usage = model.find("ToasterDemo::timely")
assert req_usage is not None
print(f"requirement usage kind: {req_usage.kind}")
for e in model.query():
d = e.as_dict()
if d.get("@type") == "RequirementUsage":
print(f"RequirementUsage: {d['qualifiedName']}")requirement usage kind: requirementUsage
RequirementUsage: ToasterDemo::timely
timely now applies TimelyToast’s constraint. What remains open is whether the measure it constrains, toast time, is a measure of effectiveness (the user’s acceptance) or a measure of performance (an engineering figure with a threshold derived from something else). That split is a modeling judgment, not a fact the model states. The record below states who cares and which framing the measure takes.
Before AC-C03 states its claim, the next cells give it a real anchor in the model: a ReviewRecordRef tag naming timely as the element the judgment is about.
# what is being claimed, and what, specifically, it is about
subject_ref = "ToasterDemo::timely"
AC_C03_TAG = """\
metadata acC03Tag : ReviewRecordRef about timely {
identifier = "AC-C03";
}
"""
print(AC_C03_TAG)metadata acC03Tag : ReviewRecordRef about timely {
identifier = "AC-C03";
}
This fragment, together with the ReviewRecordRef definition carried forward from Chapter 2, is the same text now committed in models/ch03-cumulative.sysml onward; loading the cumulative model (the model.ok cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust.
TOASTER_INCREMENT = f"{TIMELY_USAGE}\n{AC_C03_TAG}"AC-C03 states the claim first: what timely is being framed as, and which model element the framing applies to.
from toaster.evidence import ReviewRecord, validate_record, hash_content
claim = ("timely (TimelyToast) is framed as a measure of effectiveness: an "
"acceptance criterion for the user's kitchen workflow, not an "
"engineering performance figure derived from a lower-level measure.")
model_ref = ("ToasterDemo::timely")
print(claim)timely (TimelyToast) is framed as a measure of effectiveness: an acceptance criterion for the user's kitchen workflow, not an engineering performance figure derived from a lower-level measure.
The claim needs a standard to be judged against. scope names where in the model this applies; criteria states, in plain terms, what would make the claim MoE versus MoP (Hawkins’ appropriateness: is this the right frame for this measure?).
scope = ("ToasterDemo")
criteria = ("MoE if the split names who cares and frames the measure as "
"acceptance; MoP if its threshold is derived from a stated MoE with a "
"means of checking (SEBoK's MoE/MoP distinction, PDF 1562).")
print(criteria)MoE if the split names who cares and frames the measure as acceptance; MoP if its threshold is derived from a stated MoE with a means of checking (SEBoK's MoE/MoP distinction, PDF 1562).
Next, what the framing takes as given. premises is empty here (the framing does not rest on any prior derivation); assumption_refs names the one modeling judgment it does rest on.
premises = ([])
assumption_refs = ([
"The MoE/MoP split for toast timing is a case-specific modeling "
"judgment, not a fixed rule.",
])
print(assumption_refs)['The MoE/MoP split for toast timing is a case-specific modeling judgment, not a fixed rule.']
evidence_refs points at what actually supports the claim: the requirement’s own rationale text, not a computed value (there is nothing to compute for a framing judgment). rationale is the argument connecting that evidence to the claim (Hawkins’ sufficiency: is this evidence enough?).
evidence_refs = ([
"ToasterDemo::TimelyToast doc: the rationale argues from kitchen "
"workflow timing, naming the user as who cares.",
])
rationale = ("TimelyToast's rationale argues from the user's kitchen workflow, not "
"from a solution class or a lower-level performance figure: it names "
"who cares (the user) and frames the 180-second bound as part of what "
"the user accepts, not an engineering figure derived from another "
"measure.")
print(rationale)TimelyToast's rationale argues from the user's kitchen workflow, not from a solution class or a lower-level performance figure: it names who cares (the user) and frames the 180-second bound as part of what the user accepts, not an engineering figure derived from another measure.
Finally, the challenge. A framing judgment is contestable by nature, so counterevidence states the strongest case for the other framing, and residual_uncertainties says plainly that this is not settled (Hawkins’ trustworthiness: a record that hid this would be less trustworthy, not more).
counterevidence = ("TimelyToastTest's doc checks the bound as a timed test at a stated "
"input condition ('nominal input power'), which reads like an "
"engineering performance test rather than an acceptance criterion. "
"Toast time could reasonably be framed either way.")
residual_uncertainties = ("This split is a contestable modeling judgment, not a settled fact. A "
"later chapter that derives cycle time from the mechanism and the "
"energy balance may instead introduce a genuine MoP threshold derived "
"from this MoE.")
print(counterevidence)
print(residual_uncertainties)TimelyToastTest's doc checks the bound as a timed test at a stated input condition ('nominal input power'), which reads like an engineering performance test rather than an acceptance criterion. Toast time could reasonably be framed either way.
This split is a contestable modeling judgment, not a settled fact. A later chapter that derives cycle time from the mechanism and the energy balance may instead introduce a genuine MoP threshold derived from this MoE.
With every part named above, the record assembles from them directly.
from toaster.query import get_review_record_refs
from toaster.judgment_store import save_record
framing_record = ReviewRecord(
identifier="AC-C03",
kind="asserted_context",
claim=claim,
subject_ref=subject_ref,
model_ref=model_ref,
content_hash=hash_content(source),
scope=scope,
criteria=criteria,
premises=premises,
assumption_refs=assumption_refs,
evidence_refs=evidence_refs,
rationale=rationale,
counterevidence=counterevidence,
residual_uncertainties=residual_uncertainties,
disposition="pending",
dependency_freshness="current",
engineering_conclusion="undetermined",
record_kind="worked_example",
)
errors = validate_record(framing_record, model=model)
save_record(framing_record)
tag = next((t for t in get_review_record_refs(model) if t["identifier"] == framing_record.identifier), None)
print(f"Model tag: {tag}")
print(f"Validation errors: {errors}")
print(f"Record kind: {framing_record.kind}")
conn.close()Model tag: {'tag': 'ToasterDemo::acC03Tag', 'identifier': 'AC-C03', 'annotated_element': 'ToasterDemo::timely'}
Validation errors: []
Record kind: asserted_context
The tag printed above is now part of the loaded model, and validate_record confirms the Python record and the model’s own tag agree about what AC-C03 is actually about.
requirement timely : TimelyToast; printed above loaded without error, and model.find() returns its symbol, confirmed as a RequirementUsage by model.query() above.
Try the chapter exercise in exercises/ch03/exercise.ipynb: add requirement tempCheck : TemperatureReq; to your coffee maker model, then record whether it is a measure of effectiveness or a measure of performance, following the pattern above.