Skills › Scientific Agent Skills › iso-standards-readiness › check_supplier_controls.py
check_supplier_controls.py
166 lines · skills/iso-standards-readiness/scripts/check_supplier_controls.py at 5f2bc03
1#!/usr/bin/env python3
2"""Check supplier and outsourced-process control evidence."""
4from __future__ import annotations
6from typing import Any
8from _common import (
9 Review,
10 finish,
11 guarded_main,
12 load_json,
13 require_root_object,
14 standard_parser,
15)
17SUPPLIER_STATUSES = {"candidate", "approved", "conditional", "suspended", "disqualified"}
18CRITICALITIES = {"low", "medium", "high", "critical"}
19CONTROL_STATUSES = {"planned", "implemented", "verified", "not-applicable"}
22def _control(
23 review: Review,
24 supplier: dict[str, Any],
25 key: str,
26 path: str,
27 *,
28 required: bool,
29) -> None:
30 control_path = f"{path}.{key}"
31 control = review.object(supplier.get(key), control_path)
32 if control is None:
33 return
34 status = review.choice(control, "status", CONTROL_STATUSES, control_path)
35 review.text(control, "owner", control_path, max_chars=200)
36 review.text(control, "method", control_path, max_chars=2_000)
37 review.evidence(
38 control,
39 control_path,
40 min_items=1 if status in {"implemented", "verified", "not-applicable"} else 0,
41 )
42 review.approval(
43 control,
44 control_path,
45 require_approved=status in {"verified", "not-applicable"},
46 )
47 review.source_refs(control, control_path)
48 if status == "not-applicable":
49 review.text(control, "rationale", control_path, max_chars=2_000)
50 if required and status not in {"implemented", "verified"}:
51 review.add(
52 "CONTROL_NOT_READY",
53 f"{control_path}.status",
54 "risk-based supplier control must be implemented or verified",
55 "blocker",
56 )
59def validate(data: dict[str, Any]) -> tuple[Review, dict[str, int]]:
60 review = Review()
61 metadata = review.object(data.get("metadata"), "metadata")
62 if metadata is not None:
63 review.controlled_item(metadata, "metadata", require_approved=True)
64 review.text(metadata, "register_id", "metadata", max_chars=120)
65 review.date(metadata, "review_date", "metadata")
67 methodology = review.object(data.get("control_methodology"), "control_methodology")
68 if methodology is not None:
69 review.text(methodology, "risk_method", "control_methodology", max_chars=2_000)
70 review.text(
71 methodology,
72 "approval_authority",
73 "control_methodology",
74 max_chars=200,
75 )
76 review.text(
77 methodology,
78 "monitoring_and_escalation",
79 "control_methodology",
80 max_chars=2_000,
81 )
82 review.text(methodology, "owner", "control_methodology", max_chars=200)
83 review.evidence(methodology, "control_methodology")
84 review.approval(
85 methodology,
86 "control_methodology",
87 require_approved=True,
88 )
89 review.source_refs(methodology, "control_methodology")
91 suppliers = review.list(data.get("suppliers"), "suppliers", min_items=1) or []
92 approved = 0
93 for index, value in enumerate(suppliers):
94 path = f"suppliers[{index}]"
95 supplier = review.object(value, path)
96 if supplier is None:
97 continue
98 review.text(supplier, "id", path, max_chars=120)
99 review.text(supplier, "name", path, max_chars=300)
100 review.text(supplier, "owner", path, max_chars=200)
101 status = review.choice(supplier, "status", SUPPLIER_STATUSES, path)
102 criticality = review.choice(supplier, "criticality", CRITICALITIES, path)
103 review.text(
104 supplier,
105 "product_service_or_process",
106 path,
107 max_chars=1_000,
108 )
109 review.text(supplier, "risk_rationale", path, max_chars=2_000)
110 review.date(supplier, "next_review_date", path)
111 review.evidence(supplier, path)
112 review.approval(supplier, path, require_approved=status == "approved")
113 review.source_refs(supplier, path)
114 if status == "approved":
115 approved += 1
117 strict = criticality in {"high", "critical"} and status in {
118 "approved",
119 "conditional",
120 }
121 for key in (
122 "selection_and_initial_evaluation",
123 "purchasing_requirements",
124 "change_notification",
125 "incoming_or_acceptance_verification",
126 "performance_monitoring",
127 "reevaluation",
128 "nonconformity_and_capa",
129 ):
130 _control(review, supplier, key, path, required=strict)
131 for key in (
132 "quality_agreement",
133 "subtier_controls",
134 "business_continuity",
135 ):
136 _control(
137 review,
138 supplier,
139 key,
140 path,
141 required=criticality == "critical" and status in {"approved", "conditional"},
142 )
144 if status == "conditional":
145 review.text(supplier, "conditions", path, max_chars=2_000)
146 review.date(supplier, "condition_due_date", path)
147 if status in {"suspended", "disqualified"}:
148 review.text(supplier, "disposition", path, max_chars=2_000)
150 review.unique_ids(suppliers, "suppliers")
151 return review, {"approved_suppliers": approved, "suppliers": len(suppliers)}
154def main() -> int:
155 parser = standard_parser(
156 "Check risk-based supplier and outsourced-process control evidence.",
157 "Path to the local supplier-controls JSON file",
158 )
159 args = parser.parse_args()
160 data = require_root_object(load_json(args.input))
161 review, metrics = validate(data)
162 return finish("check_supplier_controls", review, args, metrics=metrics)
165if __name__ == "__main__":
166 guarded_main(main)