Spaces:
Build error
Build error
Commit History
-
Wire get_graph_depth() into 5 internal investigator queries (R-5, partial)
What: 5 hardcoded [*1..2] variable-length Cypher traversals -- 3 in ai/deep_investigator.py (_layer_3_patterns x2, _layer_4_timeline x1), 1 in ai/investigators/historical_investigator.py, 1 in api/routes/risk.py's ghost-company detection -- now interpolate int(get_graph_depth()) instead of a hardcoded 2, using the same f-string-into-Cypher-variable-length-path pattern ai/graph_analytics.py already established elsewhere in this codebase (Cypher does not accept *min..max as a query parameter -- this project's own BUG-1 fix already established that literal string interpolation is required here, not ). Why these 5 specifically, and why this is genuinely safe (unlike investigation_layers, deliberately NOT touched in this commit): every one of these 5 sites was hardcoded at exactly *1..2, which is also PROFILE['low']'s own graph_depth value. That means a low-tier/desktop install sees ZERO behavioral change from this commit -- it already was getting depth-2 traversal today, for everyone, reg…f064888
-
fix: desktop-mode-adapt dossier output; unify 3 hardcoded copies of its path (P-1)
Root cause: the dossier/export flow had this same base path hardcoded independently in THREE places, all of which need to agree for the export endpoint to work at all: 1. ai/dossier_generator.py's generate() computed its own <project_root>/data as a path-traversal allowlist (a pre-existing H-07/CodeQL security fix) and joined output_dir onto <project_root> directly. 2. api/routes/export.py passed the literal string "data/processed" as that function's output_dir argument. 3. api/routes/export.py ALSO independently recomputed <project_root>/data/processed as its OWN separate, independent path-traversal allowlist (a second H-07/CodeQL fix) to validate the path generate() handed back before serving the file. New finding while implementing this: (1) and (3) were ALREADY subtly inconsistent with each other before any desktop-mode change -- (1) allowed anything under <project_root>/data/ (e.g. data/samples), while (3) allowed only <project_root>/data/…7650d33 -
fix: store case_memory.json in app-data dir in desktop mode (P-1)
Root cause: CASE_STORE_FILE was always computed relative to this file's own location (effectively the install directory), regardless of deployment mode. case_memory.json is the one file in the audit's Track 2 Sec 10.3 path-handling finding that holds real, accumulated, non-regeneratable user data (investigation history, learned patterns, recorded false positives) -- everything else in that finding (scraped data, logs) can be regenerated or safely lost. Trigger condition: any desktop-build update process that replaces or reinstalls the app's install directory (the normal shape of a desktop app update) would silently delete this file with zero warning to the user, since it lived inside that directory. Fix: in desktop mode, store it via the existing get_app_data_dir() helper (built in D1, previously used only for the admin-secret config file) instead. Web-deployment behavior is unchanged when BHARATGRAPH_DESKTOP_MODE is unset -- same exact path as before. Verified: confirmed the web-mo…af4b9c1
-
fix: wire 3 hardcoded AI models into the existing model_selector tiering (R-2/R-3/R-4)
Root cause: config/model_selector.py's get_model() (low/medium/high variants per task) has existed since Phase 31 but was called nowhere in the codebase -- confirmed via grep, zero call sites outside its own file and tests. Three consumers hardcoded a fixed model regardless of the machine's detected runtime_profile tier: - ai/nlp_extractor.py: always en_core_web_sm (get_model('ner')'s medium-tier value -- correct only by coincidence on medium tier) - ai/shadow_draft_detector.py: always all-MiniLM-L6-v2 (same coincidental medium-tier match) - processing/entity_resolver_v2.py: always paraphrase-multilingual-MiniLM-L12-v2 -- get_model('embeddings')'s HIGH-tier value, unconditionally, on every tier including 'low'. This is the most severe of the three: the 2GB-RAM 'low' tier -- the exact case runtime_profile.py exists to protect -- was loading the single heaviest embedding model available, every time. Trigger condition: always, independent of any misconfi…3066feb
-
fix(asset_investigator): surface declared liabilities, not just assets
Root cause: liab_str was computed with the exact same formatting logic as assets_str one line above it, but only assets_str was ever appended to the positive findings list. liab_str was silently discarded despite being fetched and formatted identically. Fix: mirrored the existing assets_str surfacing pattern for liab_str, using the same any(c.isdigit() for c in ...) guard already established for assets_str. Verified: py_compile passes. Built a mock session returning assets='5.2 Crore', liabilities='1.1 Crore', called investigate() directly, and confirmed the positive findings list now contains a liabilities statement in addition to the existing assets statement.f25c8e1 -
fix(network_investigator): surface PageRank and risk_score instead of silently discarding them
Root cause: pr (PageRank) was fetched from the graph query result and assigned to a variable, then never referenced in any finding. The same query's RETURN clause also fetched n.risk_score AS rs, which wasn't even assigned to a variable at all -- a second instance of the same 'computed, never used' pattern that pyflakes doesn't catch, since it's a Cypher-level unused column rather than a Python-level unused variable. Betweenness centrality, the other metric this investigator computes, was already correctly surfaced. Fix approach, and why it's NOT a new severity-graded finding: no other consumer in this codebase thresholds PageRank as an anomaly signal (checked: ai/graph_analytics.py and api/routes/graph.py only ever rank/sort by it, never gate a risk judgment on an absolute cutoff). Inventing a 'high PageRank' finding with a made-up threshold would be a fabricated risk judgment, not a mechanical fix -- exactly the kind of guessed severity call this project's own conventions avoid. Ins…4a0a326 -
fix(timeline_builder): log 3 previously-silent exception handlers
Root cause: 3 of 6 fetch methods (_fetch_court_events, _fetch_press_events, _fetch_corporate_events) caught Exception as e and returned [] without ever referencing e -- the error simply vanished, and the caller received an empty list indistinguishable from 'there was genuinely nothing to find.' The other 3 methods in this same file (_fetch_election_events, _fetch_contract_events, _fetch_audit_events) already correctly log via logger.warning(f'...: {e}'); these 3 didn't match that pattern. Verified via a repo-wide check before fixing: of 142 'except Exception as e:' blocks in the codebase, these were among the small minority that neither log nor return the exception to the caller. Fix: mirrored the existing logging pattern from the other 3 methods in this file exactly (same f-string shape, same logger.warning call). Verified: py_compile passes. Built a mock driver whose session.run() always raises, called all 3 previously-silent methods directly, and confirmed via a captured loguru s…5a51b25
-
fix(math_investigator): Benford integration was completely dead -- wrong class name, wrong query shape, wrong output keys
NEW 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 an…aa20ab9 -
fix(benfords_analyzer): rename output key chi_squared -> chi2_statistic
Root cause: analyze() returned its result under the key "chi_squared". Both real consumers expect "chi2_statistic": api/routes/risk.py:183 does bf.get("chi2_statistic", 0) or 0, and tests/test_phase34_forensics.py asserts "chi2_statistic" in result. dict.get() with a default silently absorbed the KeyError that would otherwise have made this obvious immediately. Trigger condition: always -- a pure naming mismatch, not conditional on anything. Blast radius: the Benford risk factor inside GET /risk/{entity_id} could never contribute to a risk score, regardless of how anomalous the underlying data actually was -- chi2 silently evaluated to 0 on every call (now separately fixed to actually parse in a prior commit on the Cypher-syntax branch; this commit fixes the key it reads once it does parse). Also fixed in the same commit, same root cause: the module's own __main__ self-test block (lines 160, 170) read result['chi_squared'] -- would have KeyError'd the moment this module was run dire…67ca8f6 -
fix(benfords_analyzer): correct get_first_digit for values with leading decimal zeros
Root cause: the original code ran .lstrip("0") BEFORE removing the decimal point, so "0.045" -> ".045" -> (after removing ".") -> "045" -> first char "0". Correct behavior: 0.045's first significant digit is 4. Trigger condition: always, for any input value v where 0 < v < 0.1 and the digit immediately after the decimal point is 0 (e.g. 0.045, 0.0032, 0.001) -- a pure function of the input value, not configuration- or attacker-dependent. Blast radius: every affected value was silently bucketed as first-digit 0, a bucket the Benford expected-distribution dict doesn't even track (digit_counts = {d: 0 for d in range(1,10)}), so it wasn't just miscategorized -- it was dropped from the sample entirely, shrinking n and skewing the chi-squared statistic, for any affidavit that declares an asset value like 'Rs. 0.045 crore' (an entirely ordinary amount for minor/movable assets). Fix: swap the order -- remove the decimal point first, then strip leading zeros. Verified: py_compile passes. Ra…cb893f6
-
fix(timeline_builder): pass entity_name into audit/press event fetchers
_fetch_audit_events and _fetch_press_events both referenced entity_name in their Cypher params, but neither method actually received it as an argument -- it only existed in the outer build() scope. Every call raised NameError, caught by the broad except Exception and turned into a silent empty list. Effect: CAG audit-report events and PIB press-release events never appeared in any entity's biography timeline, for any entity, ever. Added entity_name as a real parameter to both method signatures and threaded it through from build().23131d6
-
fix(ascii): replace all non-ASCII chars in Python source files
CI check requires all .py files (except transliteration/multilingual_ner/ translator/languages) to be pure ASCII. Pre-existing violations fixed: U+03BB lambda -> lambda_val U+2014 em-dash -> -- U+2013 en-dash -> -- U+20B9 rupee Rs -> Rs. U+2192 arrow -> -> U+2500 box-h -> - U+2502 box-v -> | U+2081 subscript -> 1fa87992 -
fix(B-26): add __iter__ and __getitem__ to FakeResult for offline compatibility
FakeResult is used when Neo4j is unreachable (cold start, offline mode). Investigators that do 'for row in session.run(...)' raised TypeError (object is not iterable). Investigators that did 'result[0]' raised AttributeError. These crashed the run_investigator() wrapper, propagating uncaught exceptions instead of gracefully returning empty findings. Added __iter__ returning iter([]) and __getitem__ raising IndexError, matching the Neo4j Result protocol for empty result sets.9e00b4a -
fix(B-04): remove duplicate MathInvestigator class definition
Same copy-paste error as BenamiInvestigator. Two identical MathInvestigator classes in the same file. Python uses the last definition. The first is permanently dead code that can never receive effective bug fixes.480903f -
fix(B-03): remove duplicate BenamiInvestigator class definition
File had two identical class BenamiInvestigator definitions. Python silently uses the last one. Both were identical so no crash occurred, but this was a maintenance hazard -- any future fix applied to the first class would be silently overridden by the second.0a34a0f -
fix(B-10): remove dead module-level investigate() with 0.3 asset fabrication
The original module-level investigate() function was dead code (shadowed by the class wrapper) but still present and dangerous. It contained: 'total_assets_crore': float(row['assets']) * 0.3 to synthesize a '5 years ago' historical value from current assets. This manufactured data was fed to the Kalman filter, producing entirely fake anomaly findings. Dead fabrication code removed entirely.fa0f8ca -
fix(B-02 step1): remove third AffidavitInvestigator class (the broken one)
Python uses the LAST class definition when a name is re-declared in the same file. The file had THREE AffidavitInvestigator classes. The third and final one contained broken Cypher: MATCH (p:Politician {id:})-[:FILED_AFFIDAVIT] The dollar-sign parameter was missing. This caused a Neo4j CypherSyntaxError on every affidavit investigation, silently caught and reported as empty findings. Removed the third class. The correct first class (line 105) now takes effect.54825a7
-
fix(INV-3): add __enter__/__exit__ to FakeSession -- investigators using 'with session.run()' no longer crash silently when DB is offline
60c253a -
fix(CodeQL#12-15): use real_out_dir for makedirs and pdf_path in dossier_generator -- original output_dir was not used post-validation
b54995d -
fix(CodeQL#10,#11,#21): remove Rs amount from affidavit_analyzer logger.success -- financial PII must not appear in plaintext logs
d83f35d
-
fix(BUG-C1): add MathInvestigator class wrapper -- all 15 investigators now importable
dfeea7b -
fix(BUG-C1): add BenamiInvestigator class wrapper
4e07a37 -
fix(BUG-C1,BUG-H1): add AffidavitInvestigator class wrapper; remove fabricated synthetic asset history
8702ccc
-
fix(CodeQL#10,#11,#21): redact entity_id in affidavit_analyzer log calls -- clear-text logging of potentially sensitive identifiers
4ec50a7 -
fix(H-07,CodeQL#12-17): use file-anchored absolute paths for path traversal guards in dossier_generator and export -- relative realpath() was CWD-dependent
6de77e6 -
fix(BUG-M9): pass driver kwarg to investigators in run_investigator -- BenamiDetector/SpectralAnalyzer need driver not session
805478a -
fix(BUG-C1): add MathInvestigator class wrapper -- all 15 investigators now importable
529f177 -
fix(BUG-C1): add BenamiInvestigator class wrapper -- resolves ImportError crashing ALL_INVESTIGATORS
4a1062b -
fix(BUG-C1,BUG-H1): add AffidavitInvestigator class wrapper; remove fabricated synthetic asset history
6bc281f
-
fix(NEW-A3 part2): encode Indian script string literals as Unicode escapes in 4 files -- CI ASCII requirement met without data loss
611725a -
fix(NEW-A3 part1): replace em-dashes/smart-quotes in 56+ Python files -- CI requires pure ASCII source
8a0fba4
-
fix(ai): add AffidavitInvestigator+BenamiInvestigator+MathInvestigator to ALL_INVESTIGATORS
Three specialist investigator modules existed (affidavit_investigator.py, benami_investigator.py, math_investigator.py) but were never imported into ALL_INVESTIGATORS in __init__.py. They never ran during the parallel synthesis, their findings never contributed to confidence scores, and they never appeared in investigation reports. Fix: imported all three and added them to ALL_INVESTIGATORS. This raises the effective investigator count from 12 to 15, matching the '15 parallel investigators' claim throughout the codebase.c5025f2 -
fix(api+ai): M-01+M-02+M-03 -- version consistency and duplicate assignments
M-01: Three different version strings for the same release: FastAPI app = 0.31.0, root() endpoint = 0.30.0, settings.py = 0.30.0 Fix: settings.py VERSION -> 0.31.0, main.py root() -> 0.31.0. M-02: entity_label = 'Unknown' was written on two consecutive lines in deep_investigator.py (copy-paste residue from the BUG-1+7 fix). Fix: kept one assignment with a clean comment. M-03: _edge_seen = set() was declared twice on consecutive lines in graph.py (same copy-paste residue). Fix: kept one declaration. Also fixed graph.py GraphResponse(depth=depth) -> depth=depth_safe so the response reflects the actual queried depth, not the raw user input.01cf12a
-
fix(encoding): strip non-ASCII chars from all modified files
All files contained UTF-8 punctuation (em dashes U+2014, arrows U+2192, curly quotes) in comments which cause UnicodeDecodeError on GitHub Actions runners that read files without explicit encoding= argument. Replaced all non-ASCII punctuation with ASCII equivalents: U+2014 em dash -> -- U+2192 arrow -> -> curly quotes -> straight quotes747e613 -
fix(ai): BUG-1+7 -- entity_label UnboundLocalError on DB exception
entity_label was assigned inside a try/except block with no default before it. When Neo4j threw any exception (timeout, transient error, network blip) the except block only logged a warning but entity_label was never set. Execution continued to 'entity_type: entity_label' -- Python raised UnboundLocalError crashing the entire investigation endpoint with a raw 500. Fix: added entity_label = 'Unknown' before the try block so it is always bound regardless of exception path.3c2c226 -
fix(ai): BUG-1+7 -- entity_label UnboundLocalError on DB exception
entity_label was assigned inside a try/except block with no default before it. When Neo4j threw any exception (timeout, transient error, network blip) the except block only logged a warning but entity_label was never set. Execution continued to 'entity_type: entity_label' -- Python raised UnboundLocalError crashing the entire investigation endpoint with a raw 500. Fix: added entity_label = 'Unknown' before the try block so it is always bound regardless of exception path.fa5e7e8
-
refactor(ai): multi_investigator reads max_workers from PROFILE
Removed hardcoded max_workers=6. Now reads from _RUNTIME_PROFILE[max_workers] which resolves to: 2 on HuggingFace free tier (low profile) 4 on standard 4-core server (medium profile) 8 on high-performance server (high profile)5733584
-
fix(ai): BUG-8/11 — confidence threshold, FakeSession warning
BUG-8: synthesise() filter was 'count >= 3' which silently dropped all MODERATE findings (agreed by exactly 2 investigators). Changed to '>= 2'. MODERATE confidence level is now reachable in the if/elif chain. BUG-11: FakeSession is now used with an explicit logger.warning so operators know the DB is offline when investigators return empty results.7391f78
-
fix(BUG-18/19): timeline audit events use entity_name not entity_id; fulltext index covers all 16 node types
e728463 -
fix(BUG-15): each investigation layer now has its own Neo4j session — isolated, independently failable
9a1c2f4
-
fix(BUG-04): lambda closure — all 6 investigation layers were running the same function
5852df6 -
fix(i18n): correct garbled Gujarati no_data string
ec78dc9
-
fix(investigation): richer 6-layer queries, confidence scoring, WHY explanations
deep_investigator.py: - 6 rewritten Cypher queries with pattern detection (director→contract, audit proximity, electoral bond proximity) - confidence scoring: 0-100 with level (HIGH/MODERATE/LOW/INSUFFICIENT_DATA) - returns confidence_level and confidence_reason on every investigation connection_mapper.py: - WHY explanation on every relationship type - source attribution: which government dataset produced this edge - next_leads: 3 investigation suggestions per connection - risk_factors derived from criminal cases + edge types - coverage_note when graph is empty (explains how to load data)7b27c41
-
feat(ai): complete — Multi-Agent Debate Engine
ai/debate_engine.py: iMAD-style structured debate with 7 agent profiles Agents: logical_analyst, skeptic, pattern_hunter, timeline_analyst, financial_analyst, contrarian, risk_analyst. Each has configurable skepticism, strictness, creativity, and weight parameters. Adaptive routing: simple cases (1 LOW finding) use 3-agent panel. Moderate cases use 5 agents. Complex cases use all 7 agents. Hesitation detection: 5 regex patterns scan findings for linguistic uncertainty. High hesitation count triggers more debate rounds. 3-round structured debate: each round agents adjust confidence based on peer disagreement. Skeptic and contrarian maintain independence. Anti-drift mechanism: after each round, position similarity is measured. If agents converge too quickly (similarity > 0.85), skeptic and contrarian confidence is reduced to force re-evaluation. Consensus: majority verdict with agreement rate and average confidence. All dissenting positio…ef9f819
-
fix(api): search product field, max_hops, evidence coalesce, sources, stats
3f059ef -
fix(security): remove cleartext logging and info exposure — CodeQL alerts 4-11
b6a8cbc -
fix(security): path traversal guards on file output paths — CodeQL alerts 12-16
86a78a9
-
feat(ai): complete — Adversarial Counterevidence and Competing Hypotheses
ai/adversarial_engine.py: forced counterevidence engine For every HIGH-severity finding, the engine generates a primary hypothesis (finding as stated) and a contra hypothesis (alternative innocent explanation), then evaluates evidence for each. Contra templates: 8 finding types with 2 contra explanations each (contract_concentration, ghost_company, affidavit_wealth, granger_causality, bid_document_similarity, cooling_off_violation, benami, default fallback) Confidence scoring: contra confidence starts at 0.15 for HIGH findings, 0.30 for MODERATE, 0.45 for LOW. Database counterevidence raises it by 0.15 per piece found. Verdicts: PRIMARY_HYPOTHESIS_SUPPORTED when primary/contra ratio exceeds 3.0. INCONCLUSIVE when ratio is between 0.8 and 1.5. CONTRA_HYPOTHESIS_PROBABLE below 0.8. Overall assessment: FINDINGS_SUPPORTED, FINDINGS_WEAKENED, or INCONCLUSIVE across all hypotheses in the report. api/routes/adversarial.py: GET /adversarial…10ac2cc -
fix(ai/forensics): replace empty policy_benefit_analyzer.py with full implementation
Root cause: the file was committed as 0 bytes because the heredoc content was not written to disk before git add was run on the previous attempt. PolicyBenefitAnalyzer class now contains full implementation: - Granger causality test (F-statistic vs threshold 2.5) - Transfer entropy via discretised probability estimation - Cumulative Abnormal Contract Award (CACA) ratio scoring - Sample data fallback when database unavailable - Confirmed working: CACA 3.15x detected on test datac179f19
-
feat(ai/forensics): complete — Policy-Benefit Causal Analysis
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 databas…66297ad