Apply the finding engine to schematic and PCB reviews.
FACT, REQUIREMENT, and INFERENCE are separate fields; datasheet provenance lives in the rule DB so Recommended cannot stay ERROR; LLM output is REVIEW. Designer decisions persist across rescans. Schematic and PCB share one library and the same finding object in the report UI.
This commit is contained in:
@@ -11,6 +11,12 @@ from fastapi import APIRouter, HTTPException, Request
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.periscopex.finding_engine import (
|
||||
apply_decisions,
|
||||
complete_findings,
|
||||
decision_from_review,
|
||||
upsert_decision,
|
||||
)
|
||||
from backend.periscopex.models import Finding
|
||||
from backend.periscopex.review_workflow import (
|
||||
ReviewError,
|
||||
@@ -49,6 +55,20 @@ async def get_report(project_id: str, request: Request):
|
||||
merged = merge_schema_pcb_reports(schema, pcb)
|
||||
if merged is None:
|
||||
raise HTTPException(404, "Report not found — run the pipeline first")
|
||||
findings = _findings_from_report(merged)
|
||||
complete_findings(findings)
|
||||
dec_key = f"{prefix}/decisions.json"
|
||||
if storage.exists(dec_key):
|
||||
try:
|
||||
apply_decisions(findings, storage.read_json(dec_key) or [])
|
||||
except Exception:
|
||||
pass
|
||||
merged["findings"] = [json.loads(f.model_dump_json()) for f in findings]
|
||||
summary = {"ERROR": 0, "WARNING": 0, "INFO": 0, "total": len(findings)}
|
||||
for f in findings:
|
||||
if f.status in summary:
|
||||
summary[f.status] += 1
|
||||
merged["summary"] = summary
|
||||
return JSONResponse(merged)
|
||||
|
||||
|
||||
@@ -148,7 +168,17 @@ async def put_finding_review(project_id: str, finding_id: str, body: ReviewBody,
|
||||
owner_user_id, _ = await resolve_or_404(request, project_id)
|
||||
user_id = get_user_id(request)
|
||||
key, report_data = _load_report(storage, owner_user_id, project_id)
|
||||
ids = {f.finding_id for f in _findings_from_report(report_data) if f.finding_id}
|
||||
prefix = proj_svc.project_prefix(owner_user_id, project_id)
|
||||
findings = _findings_from_report(report_data)
|
||||
ids = {f.finding_id for f in findings if f.finding_id}
|
||||
if finding_id not in ids:
|
||||
pcb_key = f"{prefix}/pcb_report.json"
|
||||
if storage.exists(pcb_key):
|
||||
pcb_data = storage.read_json(pcb_key)
|
||||
pcb_findings = _findings_from_report(pcb_data)
|
||||
if finding_id in {f.finding_id for f in pcb_findings if f.finding_id}:
|
||||
key, report_data, findings = pcb_key, pcb_data, pcb_findings
|
||||
ids = {f.finding_id for f in findings if f.finding_id}
|
||||
if finding_id not in ids:
|
||||
raise HTTPException(404, "Finding not found")
|
||||
try:
|
||||
@@ -164,6 +194,22 @@ async def put_finding_review(project_id: str, finding_id: str, body: ReviewBody,
|
||||
raise HTTPException(400, str(exc)) from exc
|
||||
report_data["review_states"] = states
|
||||
storage.write_json(key, report_data)
|
||||
if body.state in {"wontfix", "false_positive"}:
|
||||
found = next(
|
||||
(f for f in _findings_from_report(report_data) if f.finding_id == finding_id),
|
||||
None,
|
||||
)
|
||||
if found is not None:
|
||||
dec = decision_from_review(
|
||||
found, state=body.state, reason=body.reason, user_id=user_id,
|
||||
)
|
||||
if dec is not None:
|
||||
prefix = proj_svc.project_prefix(owner_user_id, project_id)
|
||||
dkey = f"{prefix}/decisions.json"
|
||||
existing = storage.read_json(dkey) if storage.exists(dkey) else []
|
||||
if not isinstance(existing, list):
|
||||
existing = []
|
||||
storage.write_json(dkey, upsert_decision(existing, dec))
|
||||
return JSONResponse(states.get(finding_id) or {"state": "open", "reason": ""})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user