Spaces:
Build error
fix(math_investigator): Benford integration was completely dead -- wrong class name, wrong query shape, wrong output keys
Browse filesNEW FINDING -- not in the original audit report. Discovered while
checking every consumer of BenfordsAnalyzer before renaming its
chi_squared key in the prior commit (grep -rn for
'benfords_analyzer|BenfordsAnalyzer|BenfordAnalyzer' across the whole
tree, not just the two call sites the audit named).
Four separate, compounding bugs, all in ai/investigators/math_investigator.py:
1. Wrong class name: 'from ai.benfords_analyzer import BenfordAnalyzer'
(no trailing s) -- the real class is BenfordsAnalyzer. This is an
ImportError on every single call, always, regardless of data.
Reproduced directly: attempting the old import raises
"ImportError: cannot import name 'BenfordAnalyzer'". Swallowed by
this function's own broad 'except Exception as e: logger.warning(...)',
so the entire Benford block has never executed even once.
2. Wrong query shape: fetched a single scalar
(p:Politician).total_assets_crore -- at most one row per entity.
analyze() operates on a list and requires len(values) >= 20
(a named, documented threshold in benfords_analyzer.py, not
incidental). A 0-or-1-element list can never reach that threshold,
so even with bug #1 fixed, this would have silently done nothing,
forever, for any entity. Fixed by querying
(p)-[:FILED_AFFIDAVIT]->(a:Affidavit) for a.total_assets_crore
across all of an entity's filed affidavits -- the same
multi-value-per-entity pattern already established (and just fixed)
in api/routes/risk.py's own Benford block.
3. Wrong result keys: checked br.get("anomaly") -- analyze() never
returns a key called "anomaly", only "anomaly_detected". This
condition was always falsy, so even if bugs #1 and #2 were somehow
not present, no finding would ever have been appended.
4. Fabricated field: the evidence string read
f"p-value: {br.get('p_value',1.0):.4f}" -- analyze() has never
returned a p_value key anywhere in its history (verified: no such
key appears anywhere in ai/benfords_analyzer.py). Replaced with two
fields analyze() actually produces: confidence and the count of
anomalous_digits.
Also updated the chi_squared reference in the same evidence list to
chi2_statistic, matching the prior commit's rename.
Verified: py_compile passes. Confirmed the old import genuinely raises
ImportError (reproduced directly, not assumed). End-to-end test with a
mocked Neo4j session: fed 30 manipulated (clustered-near-threshold)
affidavit asset values through the real investigate() function --
previously impossible to even reach this code path -- and confirmed it
now surfaces a benford_anomaly finding (chi-squared=625.59, flagged).
Fed 30 naturally-distributed values through the same path: 0 false
positives. Fed 3 values (below the 20-minimum): no crash, correctly
produces no finding via the insufficient_data path.
|
@@ -65,17 +65,20 @@ def investigate(entity_id: str, entity_name: str,
|
|
| 65 |
logger.warning(f"[{NAME}] Fourier analysis failed: {e}")
|
| 66 |
|
| 67 |
try:
|
| 68 |
-
from ai.benfords_analyzer import
|
| 69 |
-
ba =
|
| 70 |
if session:
|
| 71 |
rows = session.run(
|
| 72 |
-
"
|
|
|
|
|
|
|
|
|
|
| 73 |
id=entity_id
|
| 74 |
).data()
|
| 75 |
assets = [r["assets"] for r in rows if r.get("assets")]
|
| 76 |
if assets:
|
| 77 |
br = ba.analyze(assets)
|
| 78 |
-
if br.get("
|
| 79 |
findings.append({
|
| 80 |
"type": "benford_anomaly",
|
| 81 |
"severity": "MODERATE",
|
|
@@ -83,8 +86,9 @@ def investigate(entity_id: str, entity_name: str,
|
|
| 83 |
"Benford's Law analysis flags statistical anomaly "
|
| 84 |
"in declared asset figures."
|
| 85 |
),
|
| 86 |
-
"evidence": [f"Chi-squared: {br.get('
|
| 87 |
-
f"
|
|
|
|
| 88 |
})
|
| 89 |
except Exception as e:
|
| 90 |
logger.warning(f"[{NAME}] Benford analysis failed: {e}")
|
|
|
|
| 65 |
logger.warning(f"[{NAME}] Fourier analysis failed: {e}")
|
| 66 |
|
| 67 |
try:
|
| 68 |
+
from ai.benfords_analyzer import BenfordsAnalyzer
|
| 69 |
+
ba = BenfordsAnalyzer()
|
| 70 |
if session:
|
| 71 |
rows = session.run(
|
| 72 |
+
"""
|
| 73 |
+
MATCH (p {id: $id})-[:FILED_AFFIDAVIT]->(a:Affidavit)
|
| 74 |
+
RETURN a.total_assets_crore AS assets
|
| 75 |
+
""",
|
| 76 |
id=entity_id
|
| 77 |
).data()
|
| 78 |
assets = [r["assets"] for r in rows if r.get("assets")]
|
| 79 |
if assets:
|
| 80 |
br = ba.analyze(assets)
|
| 81 |
+
if br.get("anomaly_detected"):
|
| 82 |
findings.append({
|
| 83 |
"type": "benford_anomaly",
|
| 84 |
"severity": "MODERATE",
|
|
|
|
| 86 |
"Benford's Law analysis flags statistical anomaly "
|
| 87 |
"in declared asset figures."
|
| 88 |
),
|
| 89 |
+
"evidence": [f"Chi-squared: {br.get('chi2_statistic',0):.2f}",
|
| 90 |
+
f"Confidence: {br.get('confidence','unknown')} "
|
| 91 |
+
f"({len(br.get('anomalous_digits',[]))} anomalous digit(s))"],
|
| 92 |
})
|
| 93 |
except Exception as e:
|
| 94 |
logger.warning(f"[{NAME}] Benford analysis failed: {e}")
|