abinazebinoy commited on
Commit
538036c
·
1 Parent(s): 5fdaa50

feat(phase-34): add api/routes/case_memory.py with 3 endpoints

Browse files

GET /case-memory/stats -- case count, pattern breakdown, false positive rates
GET /case-memory/similar -- find past cases with matching finding types
POST /case-memory/false-positive -- record false positives to improve accuracy

CaseStore was fully implemented in ai/case_memory/case_store.py.
It was imported by zero routes and had no feedback mechanism.
The false-positive endpoint closes the learning loop: users flag bad
findings, WeightOptimizer picks them up and reduces the offending
indicator weight on next optimize() run.

Files changed (1) hide show
  1. api/routes/case_memory.py +94 -0
api/routes/case_memory.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BharatGraph - Phase 34: Case Memory API
3
+ GET /case-memory/stats -- how many cases stored, pattern counts
4
+ GET /case-memory/similar -- find past cases similar to given findings
5
+ POST /case-memory/false-positive -- record a false positive to improve accuracy
6
+
7
+ Pure ASCII.
8
+ """
9
+ import os, sys
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
11
+
12
+ from datetime import datetime
13
+ from typing import List
14
+ from fastapi import APIRouter, HTTPException
15
+ from pydantic import BaseModel
16
+ from loguru import logger
17
+
18
+ router = APIRouter(prefix="/case-memory", tags=["CaseMemory"])
19
+
20
+
21
+ class FalsePositiveRequest(BaseModel):
22
+ finding_type: str
23
+ entity_id: str
24
+ reason: str = ""
25
+
26
+
27
+ @router.get("/stats")
28
+ def case_memory_stats():
29
+ """
30
+ Return how many investigation cases are stored in memory,
31
+ breakdown by finding type, and false positive rates.
32
+ """
33
+ logger.info("[CaseMemory] stats")
34
+ try:
35
+ from ai.case_memory.case_store import CaseStore
36
+ cs = CaseStore()
37
+ return {
38
+ "stats": cs.get_pattern_stats(),
39
+ "total_cases": cs.get_case_count(),
40
+ "analyzed_at": datetime.now().isoformat(),
41
+ }
42
+ except Exception as e:
43
+ logger.error(f"[CaseMemory] stats error: {type(e).__name__}")
44
+ return {"status": "error", "detail": str(type(e).__name__)}
45
+
46
+
47
+ @router.get("/similar")
48
+ def find_similar_cases(finding_types: str = ""):
49
+ """
50
+ Find past investigation cases that share the same finding types.
51
+ Pass finding_types as a comma-separated list.
52
+ Example: /case-memory/similar?finding_types=benfords_law_anomaly,ghost_company
53
+ """
54
+ logger.info(f"[CaseMemory] similar lookup: {finding_types[:60]}")
55
+ try:
56
+ from ai.case_memory.case_store import CaseStore
57
+ cs = CaseStore()
58
+ ft_list = [f.strip() for f in finding_types.split(",") if f.strip()]
59
+ findings = [{"type": ft} for ft in ft_list]
60
+ similar = cs.find_similar(findings, top_k=10)
61
+ return {
62
+ "query_finding_types": ft_list,
63
+ "similar_cases": similar,
64
+ "analyzed_at": datetime.now().isoformat(),
65
+ }
66
+ except Exception as e:
67
+ logger.error(f"[CaseMemory] similar error: {type(e).__name__}")
68
+ return {"status": "error", "detail": str(type(e).__name__)}
69
+
70
+
71
+ @router.post("/false-positive")
72
+ def record_false_positive(body: FalsePositiveRequest):
73
+ """
74
+ Record that a specific finding type produced a false positive for an entity.
75
+ This feedback is used by WeightOptimizer to reduce the weight of
76
+ over-firing indicators.
77
+ """
78
+ logger.info(f"[CaseMemory] false positive: {body.finding_type} entity={body.entity_id[:8]}")
79
+ try:
80
+ from ai.case_memory.case_store import CaseStore
81
+ cs = CaseStore()
82
+ cs.record_false_positive(
83
+ finding_type=body.finding_type,
84
+ entity_id=body.entity_id,
85
+ )
86
+ return {
87
+ "recorded": True,
88
+ "finding_type": body.finding_type,
89
+ "entity_id": body.entity_id[:8] + "...",
90
+ "recorded_at": datetime.now().isoformat(),
91
+ }
92
+ except Exception as e:
93
+ logger.error(f"[CaseMemory] false positive record error: {type(e).__name__}")
94
+ raise HTTPException(status_code=500, detail="Failed to record feedback")