Files
graph-requirements-planner/backend/app/routers/stats.py
ANDREW HOSFORD 0e8978f48c Update project files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:59:48 -05:00

61 lines
1.9 KiB
Python

from fastapi import APIRouter, Depends
from sqlalchemy import func
from sqlalchemy.orm import Session
from ..auth import verify_token
from ..database import get_db
from ..models import NodeType, RequirementNode
from ..schemas import StatsResponse
router = APIRouter(prefix="/api/stats", tags=["stats"], dependencies=[Depends(verify_token)])
@router.get("", response_model=StatsResponse)
def get_stats(project_id: int | None = None, db: Session = Depends(get_db)):
query = db.query(RequirementNode)
if project_id is not None:
query = query.filter(RequirementNode.project_id == project_id)
nodes = query.all()
total = len(nodes)
by_type = {}
by_status: dict[str, int] = {}
by_priority: dict[str, int] = {}
quality_passing = 0
leaves_with_tests = 0
total_leaves = 0
orphaned = 0
for n in nodes:
by_type[n.node_type.value] = by_type.get(n.node_type.value, 0) + 1
by_status[n.status.value] = by_status.get(n.status.value, 0) + 1
by_priority[n.priority.value] = by_priority.get(n.priority.value, 0) + 1
if n.traceable and n.measurable and n.consistent and n.single_purpose:
quality_passing += 1
if n.node_type == NodeType.leaf:
total_leaves += 1
if n.test_spec:
leaves_with_tests += 1
if n.node_type != NodeType.pillar and not n.parent_id:
orphaned += 1
coverage = (leaves_with_tests / total_leaves * 100) if total_leaves > 0 else 0.0
return StatsResponse(
total_nodes=total,
pillars=by_type.get("pillar", 0),
requirements=by_type.get("requirement", 0),
sub_requirements=by_type.get("sub_requirement", 0),
leaves=by_type.get("leaf", 0),
orphaned=orphaned,
by_status=by_status,
by_priority=by_priority,
quality_passing=quality_passing,
quality_failing=total - quality_passing,
coverage_percent=round(coverage, 1),
)