Datathon / backend /backend_app /api /risk_analysis.py
RishiXD's picture
Upload 67 files
b23ff00 verified
from fastapi import APIRouter, HTTPException
from typing import Dict, Any
from pydantic import BaseModel
from backend_app.state.store import store
router = APIRouter()
class RiskAnalysisRequest(BaseModel):
org: str
repo: str
@router.post("/analyze/risk", response_model=Dict[str, Any])
def analyze_risk(req: RiskAnalysisRequest):
"""
One-shot API to:
1. Load Live Data
2. Compute Metrics
3. Return 'Bus Factor' Risk Analysis (Feature #1)
PLUS Detailed raw stats: commits, PRs, merges per user.
"""
try:
# 1. Load Data
print(f"Loading data for {req.org}/{req.repo}...")
store.load_live_data(req.org, req.repo)
# 2. Compute
print("Computing metrics...")
store.compute()
# 3. Collect Detailed Stats
# We want: commits, comments (reviews), PRs, Merge counts per user.
stats = {}
# Structure: { "user": { "commits": 0, "prs_opened": 0, "prs_merged": 0, "reviews": 0 } }
def get_stat(u):
if u not in stats: stats[u] = {"commits": 0, "prs_opened": 0, "prs_merged": 0, "reviews": 0}
return stats[u]
# Commits
for c in store.commits:
u = c.author or "unknown"
get_stat(u)["commits"] += 1
# PRs
for p in store.prs:
u = p.author or "unknown"
get_stat(u)["prs_opened"] += 1
if p.merged_at:
get_stat(u)["prs_merged"] += 1
# Reviews
for r in store.reviews:
u = r.reviewer or "unknown"
get_stat(u)["reviews"] += 1
# Format for response
detailed_stats = []
for user, data in stats.items():
detailed_stats.append({
"user": user,
**data
})
# Sort by commits desc
detailed_stats.sort(key=lambda x: x["commits"], reverse=True)
modules = store.get_modules()
if not modules:
return {
"headline": "No activity found.",
"overall_repo_risk": 0,
"user_stats": detailed_stats,
"modules_analysis": []
}
top_risk_module = modules[0]
results = []
for mod in modules:
if not mod.people: continue
top_person = mod.people[0]
share = top_person.share_pct * 100
# Bus Factor Check
bus_factor = mod.bus_factor
insight = f"Healthy distribution."
if bus_factor == 1:
insight = f"CRITICAL: {top_person.person_id} is a single point of failure (Bus Factor 1). If they leave, {share:.1f}% of module logic is orphaned."
elif share > 50:
insight = f"HIGH RISK: {top_person.person_id} dominates ({share:.1f}%)."
results.append({
"module": mod.module_id,
"risk_score": mod.risk_index,
"severity": mod.severity,
"bus_factor": bus_factor,
"key_person": top_person.person_id,
"knowledge_share_pct": round(share, 1),
"insight": insight,
"evidence": mod.evidence
})
headline = f"Repo Analysis: {top_risk_module.module_id} is at {top_risk_module.severity} risk."
if top_risk_module.bus_factor == 1:
headline += f" {top_risk_module.people[0].person_id} is a Single Point of Failure."
return {
"headline": headline,
"overall_repo_risk": top_risk_module.risk_index,
"user_stats": detailed_stats,
"modules_analysis": results
}
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))