diff --git a/admin-compliance/app/sdk/control-library/page.tsx b/admin-compliance/app/sdk/control-library/page.tsx
index e473b29..3655f77 100644
--- a/admin-compliance/app/sdk/control-library/page.tsx
+++ b/admin-compliance/app/sdk/control-library/page.tsx
@@ -27,6 +27,11 @@ interface ControlsMeta {
domains: Array<{ domain: string; count: number }>
sources: Array<{ source: string; count: number }>
no_source_count: number
+ type_counts?: {
+ rich: number
+ atomic: number
+ eigenentwicklung: number
+ }
}
// =============================================================================
@@ -743,8 +748,9 @@ export default function ControlLibraryPage() {
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
-
-
+
+
+
|
diff --git a/backend-compliance/compliance/api/canonical_control_routes.py b/backend-compliance/compliance/api/canonical_control_routes.py
index de13988..07abe38 100644
--- a/backend-compliance/compliance/api/canonical_control_routes.py
+++ b/backend-compliance/compliance/api/canonical_control_routes.py
@@ -368,6 +368,11 @@ async def list_controls(
query += " AND decomposition_method = 'pass0b'"
elif control_type == "rich":
query += " AND (decomposition_method IS NULL OR decomposition_method != 'pass0b')"
+ elif control_type == "eigenentwicklung":
+ query += """ AND generation_strategy = 'ungrouped'
+ AND (pipeline_version = '1' OR pipeline_version IS NULL)
+ AND source_citation IS NULL
+ AND parent_control_uuid IS NULL"""
if search:
query += " AND (control_id ILIKE :q OR title ILIKE :q OR objective ILIKE :q)"
params["q"] = f"%{search}%"
@@ -450,6 +455,11 @@ async def count_controls(
query += " AND decomposition_method = 'pass0b'"
elif control_type == "rich":
query += " AND (decomposition_method IS NULL OR decomposition_method != 'pass0b')"
+ elif control_type == "eigenentwicklung":
+ query += """ AND generation_strategy = 'ungrouped'
+ AND (pipeline_version = '1' OR pipeline_version IS NULL)
+ AND source_citation IS NULL
+ AND parent_control_uuid IS NULL"""
if search:
query += " AND (control_id ILIKE :q OR title ILIKE :q OR objective ILIKE :q)"
params["q"] = f"%{search}%"
@@ -484,11 +494,34 @@ async def controls_meta():
WHERE source_citation IS NULL OR source_citation->>'source' IS NULL OR source_citation->>'source' = ''
""")).scalar()
+ # Type counts for filter dropdown
+ atomic_count = db.execute(text("""
+ SELECT count(*) FROM canonical_controls WHERE decomposition_method = 'pass0b'
+ """)).scalar() or 0
+
+ eigenentwicklung_count = db.execute(text("""
+ SELECT count(*) FROM canonical_controls
+ WHERE generation_strategy = 'ungrouped'
+ AND (pipeline_version = '1' OR pipeline_version IS NULL)
+ AND source_citation IS NULL
+ AND parent_control_uuid IS NULL
+ """)).scalar() or 0
+
+ rich_count = db.execute(text("""
+ SELECT count(*) FROM canonical_controls
+ WHERE (decomposition_method IS NULL OR decomposition_method != 'pass0b')
+ """)).scalar() or 0
+
return {
"total": total,
"domains": [{"domain": r[0], "count": r[1]} for r in domains],
"sources": [{"source": r[0], "count": r[1]} for r in sources],
"no_source_count": no_source,
+ "type_counts": {
+ "rich": rich_count,
+ "atomic": atomic_count,
+ "eigenentwicklung": eigenentwicklung_count,
+ },
}