feat(cra): coarse priority engine — P0 floor + customer weights + quick wins

Deterministic prioritisation on top of the mapper (cra_prioritizer.py): a
non-negotiable P0 floor (safety-function compromise / actively exploited /
CRITICAL — customer weights cannot demote) plus a discretionary tier ranked by
severity x the customer's weight (high/medium/low) for the 5 business objectives
(access/data/network_api/supply_updates/monitoring). Quick-win flag (high impact,
low effort) for a second view; each finding carries a short priority reason.
Endpoint accepts weights + per-finding safety_impact/exploited. Rough pre-sort
only (devs re-sort in Jira). No DB.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-14 08:21:56 +02:00
parent ad83b8dc67
commit 12fa179bfd
6 changed files with 211 additions and 8 deletions
@@ -9,7 +9,7 @@ Project-less by design: works standalone for ANY customer — including those wi
no CE risk assessment and no FMEA yet (the mandatory baseline). Reuses the fully
tested mapper; no DB, no LLM, no RAG. Same logic the MCP server exposes.
"""
from typing import List, Optional
from typing import Dict, List, Optional
from fastapi import APIRouter
from pydantic import BaseModel
@@ -28,13 +28,18 @@ class FindingIn(BaseModel):
severity: Optional[str] = ""
cvss: Optional[float] = None
location: Optional[str] = ""
safety_impact: Optional[bool] = False
exploited: Optional[bool] = False
class AssessRequest(BaseModel):
findings: List[FindingIn]
# customer priorities for the discretionary tier: {objective: high|medium|low}.
# objectives: access | data | network_api | supply_updates | monitoring.
weights: Optional[Dict[str, str]] = None
@router.post("/assess")
async def assess(body: AssessRequest):
payload = {"findings": [f.model_dump() for f in body.findings]}
payload = {"findings": [f.model_dump() for f in body.findings], "weights": body.weights}
return assess_findings_payload(payload)