ad24835940
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>
179 lines
5.9 KiB
Python
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()
|