Spaces:
Sleeping
feat(ai/forensics): complete — Policy-Benefit Causal Analysis
Browse filesai/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 +0 -0
- api/main.py +4 -3
- api/routes/policy.py +25 -0
|
File without changes
|
|
@@ -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.
|
| 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.
|
| 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 |
|
|
@@ -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)
|