"""Requirements: what the drone shall do, and whether the current design does it.A requirement here is a record in design/requirements.toml (EL01, section 2.11): [[requirement]] id = "SYS-001" text = "The drone shall have a thrust-to-weight ratio of at least 2.0 at full charge." rationale = "Hovering at or below half of maximum thrust leaves headroom to control." verification = "Analysis" # Inspection, Analysis, Demonstration, or Test result = "hover.thrust_to_weight" # the engine result it is checked against op = ">=" # ">=" or "<=" threshold = "2.0" # with a unit whenever the result has one parent = "" # the requirement this one was derived from`check` compares each requirement with the pipeline's results and reports itsstatus and margin. `validate` finds mistakes in the file itself (a missing"shall", an unknown verification method, a threshold in the wrong unit) and iswhat EL01 lab step 7 tests."""from__future__importannotationsimporttomllibfromdataclassesimportdataclassfrompathlibimportPathfromhwe.unitsimportQ_,Quantity,is_quantity,parseMETHODS=("Inspection","Analysis","Demonstration","Test")OPS=(">=","<=")@dataclassclassRequirement:id:strtext:strverification:strresult:strop:strthreshold:Quantityrationale:str=""parent:str=""@dataclassclassStatus:requirement:Requirementvalue:Quantity|Nonestate:str# "pass", "fail", "open" (no value yet), or "locked"margin:float|None# fraction by which the value beats (+) or misses (-) the thresholdnote:str=""defload(path:Path|str)->list[Requirement]:withopen(path,"rb")asf:raw=tomllib.load(f)out=[]forrinraw.get("requirement",[]):out.append(Requirement(id=r.get("id",""),text=r.get("text",""),verification=r.get("verification",""),result=r.get("result",""),op=r.get("op",""),threshold=parse(r["threshold"],default_unit="dimensionless")if"threshold"inrelseNone,rationale=r.get("rationale",""),parent=r.get("parent","")))returnoutdefvalidate(reqs:list[Requirement],known_results:dict[str,Quantity])->list[str]:"""Every problem found in the requirement records, as readable sentences."""problems=[]seen=set()forrinreqs:where=r.idor"(missing id)"ifnotr.id:problems.append("a requirement has no id")elifr.idinseen:problems.append(f"{where}: duplicate id")seen.add(r.id)if" shall "notinf" {r.text} ":problems.append(f"{where}: the text must state what the system 'shall' do")ifr.verificationnotinMETHODS:problems.append(f"{where}: verification must be one of {', '.join(METHODS)}")ifr.opnotinOPS:problems.append(f"{where}: op must be '>=' or '<='")ifr.thresholdisNone:problems.append(f"{where}: no threshold")ifr.resultandr.resultnotinknown_resultsandnotr.result.startswith("future."):problems.append(f"{where}: unknown result '{r.result}' (known: {', '.join(sorted(known_results))})")elifr.resultinknown_resultsandr.thresholdisnotNone:value=known_results[r.result]ifis_quantity(value)andvalue.dimensionality!=r.threshold.dimensionality:problems.append(f"{where}: threshold unit {r.threshold.units:~P} does not match "f"{r.result} ({value.units:~P})")returnproblemsdefcheck(reqs:list[Requirement],results:dict[str,Quantity],locked:set[str]=frozenset())->list[Status]:"""Compare each requirement with the current results."""statuses=[]forrinreqs:ifr.result.startswith("future.")orr.resultinlocked:statuses.append(Status(r,None,"locked",None,"a later module computes this"))continuevalue=results.get(r.result)ifvalueisNone:statuses.append(Status(r,None,"open",None,"not computed yet (is your core code written?)"))continuev=value.to(r.threshold.units).mifis_quantity(value)elsefloat(value)t=r.threshold.mifr.op==">=":ok,margin=v>=t,(v-t)/abs(t)iftelseNoneelse:ok,margin=v<=t,(t-v)/abs(t)iftelseNonestatuses.append(Status(r,value,"pass"ifokelse"fail",margin))returnstatuses