40-validation/validate-context-impact.py
#!/usr/bin/env python3
import argparse,json
from pathlib import Path
CHANGE_TYPES={"move","rename","validity-change"}
DECISIONS={"evidence","not-applicable","review-required"}
REQUIRED=("affectedMeanings","affectedRelationships","affectedArtifacts")
def finding(code, location, message):
return {"severity":"error","code":code,"location":location,"message":message}
def validate(data):
findings=[]; changes=data.get("changes")
if not isinstance(data.get("workItem"),str) or not data["workItem"].strip():
findings.append(finding("MISSING_WORK_ITEM","workItem","workItem muss gesetzt sein."))
if not isinstance(changes,list) or not changes:
findings.append(finding("MISSING_CONTEXT_IMPACT_EVIDENCE","changes","Mindestens ein Kontextwirkungsnachweis ist erforderlich."));changes=[]
records=[]
for index, change in enumerate(changes):
location=f"changes[{index}]"
if not isinstance(change,dict):
findings.append(finding("INVALID_CHANGE",location,"Eintrag muss ein Objekt sein."));continue
record={"id":change.get("id"),"changeType":change.get("changeType"),"decision":change.get("decision")};records.append(record)
if not isinstance(change.get("id"),str) or not change["id"].strip(): findings.append(finding("MISSING_CHANGE_ID",location,"id muss gesetzt sein."))
if change.get("changeType") not in CHANGE_TYPES: findings.append(finding("INVALID_CHANGE_TYPE",location+".changeType","changeType muss move, rename oder validity-change sein."))
for field in REQUIRED:
if not isinstance(change.get(field),list): findings.append(finding("MISSING_AFFECTED_CONTEXT",location+"."+field,f"{field} muss als Liste vorliegen."))
if not isinstance(change.get("timeContext"),str) or not change["timeContext"].strip(): findings.append(finding("MISSING_TIME_CONTEXT",location+".timeContext","Zeitkontext muss gesetzt sein."))
decision=change.get("decision")
if decision not in DECISIONS: findings.append(finding("INVALID_DECISION",location+".decision","decision muss evidence oder not-applicable sein."))
elif decision=="not-applicable" and not isinstance(change.get("reason"),str): findings.append(finding("MISSING_NOT_APPLICABLE_REASON",location+".reason","not-applicable benötigt eine Begründung."))
elif decision=="evidence" and not isinstance(change.get("evidence"),str): findings.append(finding("MISSING_CONTEXT_IMPACT_EVIDENCE",location+".evidence","evidence benötigt einen Nachweispfad oder eine Referenz."))
elif decision=="review-required" and not isinstance(change.get("reviewReason"),str): findings.append(finding("MISSING_REVIEW_REASON",location+".reviewReason","review-required benötigt eine fachlich offene Begründung."))
if not isinstance(change.get("followUpValidations"),list) or not change["followUpValidations"]: findings.append(finding("MISSING_FOLLOW_UP_VALIDATIONS",location+".followUpValidations","Mindestens eine Folgeprüfung ist erforderlich."))
status="FAILED" if findings else ("REVIEW_REQUIRED" if any(r["decision"]=="review-required" for r in records) else "PASSED")
return {"schemaVersion":"1.1","workItem":data.get("workItem"),"status":status,"contentTruthAssessed":False,"changes":records,"findings":findings}
def main():
parser=argparse.ArgumentParser();parser.add_argument("input",type=Path);parser.add_argument("--output",type=Path);args=parser.parse_args()
try: report=validate(json.loads(args.input.read_text(encoding="utf-8")))
except Exception as exc: report={"schemaVersion":"1.0","status":"FAILED","contentTruthAssessed":False,"changes":[],"findings":[finding("INVALID_INPUT",str(args.input),str(exc))]}
text=json.dumps(report,ensure_ascii=False,indent=2)+"\n"
if args.output: args.output.parent.mkdir(parents=True,exist_ok=True);args.output.write_text(text,encoding="utf-8")
else: print(text,end="")
raise SystemExit(0 if report["status"] in {"PASSED","REVIEW_REQUIRED"} else 1)
if __name__=="__main__": main()