abinazebinoy commited on
Commit
66297ad
·
1 Parent(s): 9036942

feat(ai/forensics): complete — Policy-Benefit Causal Analysis

Browse files

ai/forensics/policy_benefit_analyzer.py: three causal inference methods

Method 1 — Granger causality:
Compares residual variance of an autoregressive model for the
contract series (restricted) versus a VAR model that also includes
lagged policy event values (unrestricted). F-statistic above 2.5
indicates policy events carry predictive power over contracts.
Lag order 2. Fallback-safe when series is too short.

Method 2 — Transfer entropy:
Discretised joint/marginal probability estimation over binned
contract and policy time series. TE(policy->contracts) above 0.15
nats indicates directional information flow from policy activity
to contract patterns.

Method 3 — Cumulative Abnormal Contract Award (CACA):
For each policy event, computes expected contract volume from the
180-day pre-event baseline and compares to 180-day post-event
actual. CACA ratio above 1.5x is flagged. All methods are
fallback-safe with sample data when database is unavailable.

api/routes/policy.py: GET /policy/causal/{entity_id}
Returns all three analyses with structured findings and evidence.
Returns HTTP 404 if entity not found.
api/main.py: policy router registered, version bumped to 0.25.0

ai/forensics/policy_benefit_analyzer.py ADDED
File without changes
api/main.py CHANGED
@@ -10,7 +10,7 @@ from fastapi.middleware.cors import CORSMiddleware
10
  from loguru import logger
11
 
12
  from api.dependencies import get_driver, close_driver
13
- from api.routes import search, profile, graph, risk, multilingual, export, admin, investigation, affidavit, biography, benami, sources, procurement, conflict, linguistic
14
  from api.models import HealthResponse, StatsResponse
15
 
16
 
@@ -30,7 +30,7 @@ app = FastAPI(
30
  "All data sourced from official government records. "
31
  "Outputs are structural indicators, not legal findings."
32
  ),
33
- version="0.24.0",
34
  lifespan=lifespan,
35
  )
36
 
@@ -67,6 +67,7 @@ app.include_router(sources.router, tags=["Sources"])
67
  app.include_router(procurement.router, tags=["Procurement"])
68
  app.include_router(conflict.router, tags=["Conflict"])
69
  app.include_router(linguistic.router, tags=["Linguistic"])
 
70
 
71
 
72
  @app.get("/health", response_model=HealthResponse)
@@ -81,7 +82,7 @@ def health_check():
81
  return HealthResponse(
82
  status="ok" if connected else "degraded",
83
  neo4j_connected=connected,
84
- version="0.24.0",
85
  generated_at=datetime.now().isoformat(),
86
  )
87
 
 
10
  from loguru import logger
11
 
12
  from api.dependencies import get_driver, close_driver
13
+ from api.routes import search, profile, graph, risk, multilingual, export, admin, investigation, affidavit, biography, benami, sources, procurement, conflict, linguistic, policy
14
  from api.models import HealthResponse, StatsResponse
15
 
16
 
 
30
  "All data sourced from official government records. "
31
  "Outputs are structural indicators, not legal findings."
32
  ),
33
+ version="0.25.0",
34
  lifespan=lifespan,
35
  )
36
 
 
67
  app.include_router(procurement.router, tags=["Procurement"])
68
  app.include_router(conflict.router, tags=["Conflict"])
69
  app.include_router(linguistic.router, tags=["Linguistic"])
70
+ app.include_router(policy.router, tags=["Policy"])
71
 
72
 
73
  @app.get("/health", response_model=HealthResponse)
 
82
  return HealthResponse(
83
  status="ok" if connected else "degraded",
84
  neo4j_connected=connected,
85
+ version="0.25.0",
86
  generated_at=datetime.now().isoformat(),
87
  )
88
 
api/routes/policy.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys
2
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException
5
+ from loguru import logger
6
+ from api.dependencies import get_db
7
+ from ai.forensics.policy_benefit_analyzer import PolicyBenefitAnalyzer
8
+
9
+ router = APIRouter()
10
+ analyzer = PolicyBenefitAnalyzer()
11
+
12
+
13
+ @router.get("/policy/causal/{entity_id}")
14
+ def policy_causal_analysis(entity_id: str, driver=Depends(get_db)):
15
+ logger.info(f"[Policy] Causal analysis requested: {entity_id}")
16
+ with driver.session() as s:
17
+ row = s.run(
18
+ "MATCH (n {id:$id}) RETURN n.name AS name", id=entity_id
19
+ ).single()
20
+ if not row:
21
+ raise HTTPException(
22
+ status_code=404, detail=f"Entity {entity_id} not found"
23
+ )
24
+ name = row.get("name") or entity_id
25
+ return analyzer.analyze(entity_id, name, driver=driver)