Files
breakpilot-core/control-pipeline/api/master_control_routes.py
T
Benjamin Admin ad24835940 feat(pipeline): G-pre1/2/3 — Object Clustering + Master Controls + API
G-pre1: 144k objects clustered into 7,466 groups via Mini-Batch K-Means
  on bge-m3 embeddings. Two-stage: k=5000 base + sub-cluster groups >50.
G-pre2: 5,114 Master Controls from lifecycle phase chains
  (define→implement→test→monitor), linking 172,504 atomic controls.
G-pre3: REST API for Master Controls
  GET /v1/master-controls (list, search, filter)
  GET /v1/master-controls/stats
  GET /v1/master-controls/{mc_id} (detail with phase-controls)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 15:11:38 +02:00

179 lines
5.9 KiB
Python

"""Master Control API — G-pre3.
Provides read access to Master Controls (lifecycle-grouped atomic controls).
"""
import json
import logging
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from sqlalchemy import text
from db.session import SessionLocal
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/v1/master-controls", tags=["master-controls"])
@router.get("")
async def list_master_controls(
limit: int = Query(50, ge=1, le=500),
offset: int = Query(0, ge=0),
search: Optional[str] = None,
min_phases: Optional[int] = None,
min_controls: Optional[int] = None,
sort: str = Query("total_controls", regex="^(total_controls|phases|name|created_at)$"),
):
"""List Master Controls with optional filtering."""
db = SessionLocal()
try:
where_clauses = []
params: dict = {"limit": limit, "offset": offset}
if search:
where_clauses.append("mc.canonical_name ILIKE :search")
params["search"] = f"%{search}%"
if min_phases:
where_clauses.append("jsonb_array_length(mc.phases_covered) >= :min_phases")
params["min_phases"] = min_phases
if min_controls:
where_clauses.append("mc.total_controls >= :min_controls")
params["min_controls"] = min_controls
where = "WHERE " + " AND ".join(where_clauses) if where_clauses else ""
sort_map = {
"total_controls": "mc.total_controls DESC",
"phases": "jsonb_array_length(mc.phases_covered) DESC",
"name": "mc.canonical_name ASC",
"created_at": "mc.created_at DESC",
}
order = sort_map.get(sort, "mc.total_controls DESC")
rows = db.execute(text(f"""
SELECT mc.id, mc.master_control_id, mc.object_group_id,
mc.canonical_name, mc.phases_covered,
mc.phase_control_count, mc.total_controls,
mc.created_at
FROM master_controls mc
{where}
ORDER BY {order}
LIMIT :limit OFFSET :offset
"""), params).fetchall()
total = db.execute(text(f"""
SELECT count(*) FROM master_controls mc {where}
"""), params).scalar()
return {
"total": total,
"limit": limit,
"offset": offset,
"master_controls": [
{
"id": str(r[0]),
"master_control_id": r[1],
"object_group_id": r[2],
"canonical_name": r[3],
"phases_covered": r[4],
"phase_control_count": r[5],
"total_controls": r[6],
"created_at": str(r[7]),
}
for r in rows
],
}
finally:
db.close()
@router.get("/stats")
async def master_control_stats():
"""Aggregate statistics about Master Controls."""
db = SessionLocal()
try:
stats = db.execute(text("""
SELECT
count(*) AS total_master_controls,
sum(total_controls) AS total_member_controls,
avg(total_controls)::int AS avg_controls_per_mc,
max(total_controls) AS max_controls,
avg(jsonb_array_length(phases_covered))::numeric(3,1) AS avg_phases,
max(jsonb_array_length(phases_covered)) AS max_phases
FROM master_controls
""")).fetchone()
phase_dist = db.execute(text("""
SELECT phase, count(*) AS control_count
FROM master_control_members
GROUP BY phase
ORDER BY control_count DESC
""")).fetchall()
return {
"total_master_controls": stats[0],
"total_member_controls": stats[1],
"avg_controls_per_mc": stats[2],
"max_controls": stats[3],
"avg_phases": float(stats[4]) if stats[4] else 0,
"max_phases": stats[5],
"phase_distribution": {r[0]: r[1] for r in phase_dist},
}
finally:
db.close()
@router.get("/{mc_id}")
async def get_master_control(mc_id: str):
"""Get a single Master Control with all phase-controls."""
db = SessionLocal()
try:
mc = db.execute(text("""
SELECT mc.id, mc.master_control_id, mc.object_group_id,
mc.canonical_name, mc.phases_covered,
mc.phase_control_count, mc.total_controls
FROM master_controls mc
WHERE mc.master_control_id = :mc_id
"""), {"mc_id": mc_id}).fetchone()
if not mc:
raise HTTPException(status_code=404, detail="Master Control not found")
members = db.execute(text("""
SELECT mcm.phase, mcm.action,
cc.control_id, cc.title, cc.severity,
cc.source_citation->>'source' AS source
FROM master_control_members mcm
JOIN canonical_controls cc ON cc.id = mcm.control_uuid
WHERE mcm.master_control_uuid = CAST(:mc_uuid AS uuid)
ORDER BY mcm.phase, cc.control_id
"""), {"mc_uuid": str(mc[0])}).fetchall()
# Group by phase
phases = {}
for phase, action, ctrl_id, title, severity, source in members:
if phase not in phases:
phases[phase] = []
phases[phase].append({
"control_id": ctrl_id,
"title": title,
"action": action,
"severity": severity,
"source": source,
})
return {
"id": str(mc[0]),
"master_control_id": mc[1],
"object_group_id": mc[2],
"canonical_name": mc[3],
"phases_covered": mc[4],
"phase_control_count": mc[5],
"total_controls": mc[6],
"phases": phases,
}
finally:
db.close()