<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Subhojyoti Maity</title>
    <description>The latest articles on DEV Community by Subhojyoti Maity (@subhojyoti_maity).</description>
    <link>https://dev.to/subhojyoti_maity</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4099118%2F96b52fc9-f74c-43b8-ba5d-f4673ea59d62.jpg</url>
      <title>DEV Community: Subhojyoti Maity</title>
      <link>https://dev.to/subhojyoti_maity</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/subhojyoti_maity"/>
    <language>en</language>
    <item>
      <title>Kavach, final round: a fraud investigator that shows its work, on TigerGraph</title>
      <dc:creator>Subhojyoti Maity</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:13:17 +0000</pubDate>
      <link>https://dev.to/subhojyoti_maity/kavach-final-round-a-fraud-investigator-that-shows-its-work-on-tigergraph-52b7</link>
      <guid>https://dev.to/subhojyoti_maity/kavach-final-round-a-fraud-investigator-that-shows-its-work-on-tigergraph-52b7</guid>
      <description>&lt;p&gt;&lt;em&gt;TigerGraph x Hacker House Goa 2026, final round. Team PixelPaws: Subhojyoti Maity and Arni Bachhar.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the first round we built Kavach ("shield"), an agent that investigates card-fraud alerts on a TigerGraph graph. The final round raised the bar: 18 new alerts, 650,938 transactions (July 2016 to January 2017), the bank's 5,585 closed cases, a written Fraud Policy v1.0, and 14 challenges that read like a bank's audit checklist. This post covers what we built and how TigerGraph carries the weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: an alert is a reason to look, not a verdict
&lt;/h2&gt;

&lt;p&gt;A bank's fraud model flags a transaction. Someone then has to decide: is it fraud, how far does it go, which other cards are involved, what should the bank do, and must it file a suspicious activity report (SAR)? Wrong one way, the fraud keeps running. Wrong the other, an innocent customer gets blocked.&lt;/p&gt;

&lt;p&gt;It is tempting to paste the alert into an LLM and ask for a verdict. For a bank, that is the wrong design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It cannot be re-run.&lt;/strong&gt; An examiner asking "why did you block this card?" needs the queries, parameters and rows behind the answer, not a paragraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free text is an attack surface.&lt;/strong&gt; Six of the 18 alerts carry hostile text. A model that reads it can be steered by it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It cannot be held to a policy.&lt;/strong&gt; The policy says who may approve what, when a report is required and when to escalate. Those rules belong in code with unit tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So in Kavach, &lt;strong&gt;tools and policy decide, and the model only writes prose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every alert goes through nine fixed steps, in the same order every time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Intake.&lt;/strong&gt; The free text becomes data: quoted, redacted, zero weight. The model never sees it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gather.&lt;/strong&gt; Graph tool calls (card history, device and email neighbourhoods, closed cases), each traced with its parameters and returned ids.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detectors.&lt;/strong&gt; One-card, network, history and typology detectors, plus grown detectors a human L2 approved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence model.&lt;/strong&gt; Likelihood ratios calibrated on the closed cases, combined over independent evidence groups into a probability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy engine.&lt;/strong&gt; Fraud Policy v1.0 as code (R1 to R10, section 3a, approval routes): verdict, actions with routes (auto, L1, L2), evidence requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episode.&lt;/strong&gt; Affected transactions, connected cards, exposure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall.&lt;/strong&gt; Fused precedent, ring component and case memory, attested at zero weight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrative.&lt;/strong&gt; A facts JSON goes to the LLM (Google Gemini &lt;code&gt;gemini-3.8-flash&lt;/code&gt;), which writes the summary and SAR narrative. Every id in its text is checked; on failure a template is used and recorded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attest and persist.&lt;/strong&gt; Every claim records what, the evidence ids, how (query plus parameters) and when (timestamp plus dataset version). The case is written back to TigerGraph and read back before the answer may say &lt;code&gt;written_to_graph: true&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A harness enforces hard bounds (80 tool calls per case, 90 s per call, 15 minutes per case) and fails closed. Tools sit behind one registry with three lanes: TigerGraph installed queries (&lt;code&gt;--backend tg&lt;/code&gt;), the TigerGraph MCP server (&lt;code&gt;--backend mcp&lt;/code&gt;) and a local DuckDB mirror (&lt;code&gt;--backend duck&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  How we used TigerGraph
&lt;/h2&gt;

&lt;p&gt;The graph lives on a Savanna workspace (TigerGraph 4.2.5) at the smallest size, TG-00: 13 vertex types (from Customer, Card and Transaction to ClosedCase, InvestigationCase, CaseEvent, Approval and Detector), 650,938 transactions and about 3.6M edges, with counts checked against the source files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installed GSQL queries are the agent's tools.&lt;/strong&gt; There are 45 installed queries, all read-only. Each tool is one query with typed parameters, so "who else paid this recipient email domain?" is a traversal with accumulators, not a prompt. On the TigerGraph lane there is no fallback to DuckDB: a graph failure is an incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector search fused with traversal.&lt;/strong&gt; Closed-case notes, policy sections and investigation summaries carry 384-d embeddings searched with &lt;code&gt;vectorSearch()&lt;/code&gt;. The interesting query is &lt;code&gt;fused_precedent&lt;/code&gt;. It walks from the case card to its recent transactions, to the rare elements they carry (a device profile, recipient email domain or billing region used by 25 or fewer cards), to the other cards using them, to every closed case on those cards, then runs &lt;code&gt;vectorSearch(..., {candidate_set: X})&lt;/code&gt; over exactly that set. The fused top 5 differed from plain vector search on 13 of 13 cases with connected precedent, and from plain traversal on 9 of 13. On two legitimate alerts (HHF-014, HHF-016), traversal alone ranked five confirmed frauds first; the fused query ranked five cleared cases first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connected components for rings.&lt;/strong&gt; &lt;code&gt;ring_wcc&lt;/code&gt; runs label propagation for weakly connected components over a card graph whose edges are rare elements shared by atypical transactions in a 30-day window. Without the "atypical" restriction, the rules percolated into one 600 to 1,100 card component on every alert. With it, 8 of the 9 legitimate alerts stay single cards, and with no detector input it recovers 14 of the 16 cards in the HHF-006 ring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP, restricted to an allowlist.&lt;/strong&gt; On the MCP lane the only MCP tool called is &lt;code&gt;tigergraph__run_installed_query&lt;/code&gt;, and only for query names on an allowlist built from our own read-only GSQL files. Any other name raises &lt;code&gt;PermissionError&lt;/code&gt; before a call. No GSQL, upsert or schema tool is reachable, and the LLM never sees a tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write-back, read-back, hash chains.&lt;/strong&gt; Each investigation is upserted as an InvestigationCase with edges to its card, transactions, devices and similar closed cases. Edges use &lt;code&gt;vertex_must_exist&lt;/code&gt;, so a link to a missing id is refused instead of creating an empty vertex. Case events and approvals are written insert-only with &lt;code&gt;new_vertex_only&lt;/code&gt;; approvals form a sha256 hash chain, and the graph copy is read back and compared by count, chain hash and digest. All 18 answers carry &lt;code&gt;written_to_graph: true&lt;/code&gt; only after that read-back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The innovation: a detective that grows its own detectors
&lt;/h2&gt;

&lt;p&gt;A miner searches seven generic pattern families (258 grid points) over the 5,585 closed cases in 24 seconds, without reading the analysts' notes. It only proposes patterns that separate confirmed fraud from both normal traffic and cleared customers, with a significance test corrected for the whole search.&lt;/p&gt;

&lt;p&gt;Searching blind, it put both of the bank's undocumented typologies on its shortlist: the just-under-$500 four-purchase burst and an anonymous-proxy device ring. It also proposed one detector nobody wrote: a fan-in spike where a rarely used recipient email domain is suddenly paid by three or more cards in one day (8 confirmed-fraud cases, 0 cleared, lift 18).&lt;/p&gt;

&lt;p&gt;Nothing joins the toolkit until a human with the L2 role approves it. An analyst token was refused first and logged as denied; the lead then approved it with the L2 token. Here is what that detector changes on HHF-006, where the analyst asked what several affected cards have in common:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;arm&lt;/th&gt;
&lt;th&gt;HHF-006 decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no grown detector, no typologies&lt;/td&gt;
&lt;td&gt;uncertain, p 0.42; decline, case, monitor, escalate; no report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typologies only&lt;/td&gt;
&lt;td&gt;fraud, p 0.96, card_not_present_fraud; block and case; no ring, no report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;full system&lt;/td&gt;
&lt;td&gt;fraud, p 0.88, undocumented ring: 3 transactions ($967.68) on the case card, 16 connected cards paying recipient domain sc.rr.com; BLOCK_CARD (L1), CREATE_CASE, FILE_REPORT (L2), MONITOR_CONNECTED_CARDS, ESCALATE_TO_ANALYST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It changes no other case in the pack, so it cannot act against a legitimate customer elsewhere. On TigerGraph it runs through 13 installed GSQL mirrors and gives the identical decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilience: fail closed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Graph or tool fails mid-run:&lt;/strong&gt; one retry for a transient error, then an incident record, exit code 2, no answer file, nothing claimed as saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model times out or returns garbage:&lt;/strong&gt; template prose, fallback recorded, decisions unchanged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hostile text:&lt;/strong&gt; parsed as data with zero weight; decisions are identical with and without it, and it never reaches a prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong role tries to approve:&lt;/strong&gt; the role comes from a server-side bearer token, never the request body. The wrong role gets a 403 and a &lt;code&gt;denied&lt;/code&gt; record in the append-only chain, and the route uses the server's own exposure, so a caller cannot lower it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same case twice:&lt;/strong&gt; identical decisions and a recorded re-run event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each has a fault-injection drill (&lt;code&gt;KAVACH_FAULT=graph:down&lt;/code&gt;, &lt;code&gt;tool:any:5&lt;/code&gt;, &lt;code&gt;llm:garbage&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Our 18 answers: 7 fraud, 9 legitimate, 2 uncertain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizer grader: 18 of 18 answered, hard 0, policy 0, warnings 0.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kavach attest --all&lt;/code&gt; re-runs every attested claim: 0 failing.&lt;/li&gt;
&lt;li&gt;Decisions identical on the DuckDB and TigerGraph lanes (18 of 18, 330 of 330 identical tool outputs); the MCP lane reproduces them on the cases we re-ran through it. The same case twice gives the same answer.&lt;/li&gt;
&lt;li&gt;361 tests passing.&lt;/li&gt;
&lt;li&gt;Round 1 re-scored against the bank's real outcomes: 19 of 20 verdicts right, no legitimate customer blocked or reported.&lt;/li&gt;
&lt;li&gt;On three other banks' GSQL schemas from the mentors (ISO 20022, BIAN, T24), with the answer key stripped, our mapper got the target right on 81 of 82 items (72 of 82 when the mapping kind must also match).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Customer and analyst replies are simulated deterministically from the evidence. Every answer file states that assumption.&lt;/li&gt;
&lt;li&gt;January has no labels, so we read the January risk score as near-uninformative, and some January templates cannot be backtested.&lt;/li&gt;
&lt;li&gt;The grown-detector library is grid-bound: it searches shapes a human wrote, at grid points we chose, and the accepted detectors rest on 5 to 8 fraud cases each. One grown signal alone cannot make a verdict.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ring_wcc&lt;/code&gt; confirms rather than changes the six ring answers today. It also places a legitimate alert's card (HHF-013) in the same component as the HHF-006 ring, which a human reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we learned about TigerGraph
&lt;/h2&gt;

&lt;p&gt;What worked first time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vectorSearch()&lt;/code&gt; with a &lt;code&gt;candidate_set&lt;/code&gt; taken from a traversal. Asking for k equal to the set size returns a distance for every candidate; the ranking matched our local cosine to 4 decimals on all 18 cases.&lt;/li&gt;
&lt;li&gt;Label propagation with &lt;code&gt;MapAccum&amp;lt;STRING, MinAccum&amp;lt;INT&amp;gt;&amp;gt;&lt;/code&gt; and a &lt;code&gt;WHILE ... LIMIT&lt;/code&gt; loop compiled and ran on the first attempt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;new_vertex_only&lt;/code&gt; and &lt;code&gt;vertex_must_exist&lt;/code&gt; are exactly the append-only and no-invention guarantees an audit trail needs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CREATE OR REPLACE QUERY&lt;/code&gt; without &lt;code&gt;INSTALL&lt;/code&gt; works as a quick compile check before the slow install.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where we lost time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A query result containing the text "Runtime Error:" came back as HTTP 400 with a correct body, so a good result looked like a failed query. That cost us a debugging hour.&lt;/li&gt;
&lt;li&gt;A missing numeric value loads as 0, so null and 0 cannot be told apart afterwards.&lt;/li&gt;
&lt;li&gt;The MCP server defaults to ports 9000 and 14240, while Savanna serves everything on 443.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On our wish list: an offline GSQL lint, nullable attributes, and a bulk vector loader.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/MONSTERBOY110/hhgoa-tigergraph" rel="noopener noreferrer"&gt;https://github.com/MONSTERBOY110/hhgoa-tigergraph&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live case viewer: &lt;a href="https://monsterboy110.github.io/hhgoa-tigergraph/site/" rel="noopener noreferrer"&gt;https://monsterboy110.github.io/hhgoa-tigergraph/site/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Launch video: &lt;a href="https://x.com/kavachhhgoa/status/2116176025898168593" rel="noopener noreferrer"&gt;https://x.com/kavachhhgoa/status/2116176025898168593&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading. Every number above has a command in the repo that reproduces it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tigergraph</category>
      <category>fraud</category>
      <category>python</category>
    </item>
    <item>
      <title>Kavach: building a fraud investigator that knows when a risk score is lying</title>
      <dc:creator>Subhojyoti Maity</dc:creator>
      <pubDate>Wed, 23 Sep 2026 18:44:22 +0000</pubDate>
      <link>https://dev.to/subhojyoti_maity/kavach-building-a-fraud-investigator-that-knows-when-a-risk-score-is-lying-2id</link>
      <guid>https://dev.to/subhojyoti_maity/kavach-building-a-fraud-investigator-that-knows-when-a-risk-score-is-lying-2id</guid>
      <description>&lt;h1&gt;
  
  
  Kavach: building a fraud investigator that knows when a risk score is lying
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;TigerGraph x Hacker House Goa 2026, Task 4: Agentic Fraud Investigation. Team PixelPaws.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A bank's fraud model gives every card transaction a risk score. The task handed us 590,742 transactions, 5,565 closed investigations and 20 open alerts, and one warning in capitals: &lt;strong&gt;a risk score is a reason to look, never a verdict.&lt;/strong&gt; Half of the 20 alerts are legitimate. The job is to decide which half, how far each fraud goes, and what the bank should do next under a written fraud policy, and to write each case back into the graph so the next investigation can find it.&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;Kavach&lt;/strong&gt; ("shield"). This post covers what we built, how TigerGraph fits in, and the handful of things the data taught us that no amount of prompt engineering would have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the problem
&lt;/h2&gt;

&lt;p&gt;Every answer has three parts: an internal &lt;strong&gt;case&lt;/strong&gt; (verdict, pattern, affected transactions, connected cards, exposure, evidence), a &lt;strong&gt;suspicious activity report&lt;/strong&gt; when the policy calls for one, and the &lt;strong&gt;next best action&lt;/strong&gt; before and after the evidence the agent asks for. Everything is scored against a hidden key, and every ID has to exist in the data.&lt;/p&gt;

&lt;p&gt;That rules out letting an LLM decide anything. In Kavach, &lt;strong&gt;tools and Python decide, and the LLM writes.&lt;/strong&gt; Verdicts, amounts, IDs, actions and approval routes come from detectors, a calibrated evidence model and a policy engine. The LLM (Groq &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;) writes the summary, the SAR narrative and the description of new fraud patterns from a facts JSON. Every ID it writes is checked against those facts, and a template takes over if it slips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case -&amp;gt; GATHER -&amp;gt; DETECT -&amp;gt; ASSESS -&amp;gt; initial actions -&amp;gt; ask the customer? -&amp;gt; simulated reply
     -&amp;gt; ASSESS -&amp;gt; final actions -&amp;gt; RECALL similar cases -&amp;gt; NARRATE -&amp;gt; write back to TigerGraph -&amp;gt; answer file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GATHER&lt;/strong&gt; pulls the card's history, the customer's cards, the device profile and every other card on it, the billing region, closed cases touching any of these, and the history of the underlying account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DETECT&lt;/strong&gt; runs general detectors in three families: single-card (sequence, device, region, match flags, score, the customer's words), cross-card (device rings, fixed-amount templates, repeated bursts) and history (repeat compromise, device precedents).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASSESS&lt;/strong&gt; combines the strongest signal from each independent evidence group into a log-odds, so ten correlated device signals count once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The policy engine&lt;/strong&gt; is Fraud Policy v1.0 as code: rules R1 to R10, the approval table, and "a case is not a report" (section 3a). It is unit-tested rule by rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How we used TigerGraph
&lt;/h2&gt;

&lt;p&gt;The graph holds customers, cards, transactions, device profiles, email domains, billing regions, the bank's closed cases, and the agent's own &lt;code&gt;InvestigationCase&lt;/code&gt; vertices. Every agent tool is an installed GSQL query: &lt;code&gt;card_history&lt;/code&gt;, &lt;code&gt;device_neighbors&lt;/code&gt;, &lt;code&gt;region_activity&lt;/code&gt;, &lt;code&gt;holder_fraud_history&lt;/code&gt;, &lt;code&gt;closed_cases_for&lt;/code&gt;, &lt;code&gt;amount_peers&lt;/code&gt;. The agent runs the same code against TigerGraph (through pyTigerGraph or the TigerGraph MCP server) and against a DuckDB copy used for analysis. A parity test checks that both lanes give the same answers.&lt;/p&gt;

&lt;p&gt;The question "what else happened on this device?" is a two-hop traversal, Transaction to DeviceProfile to Transaction to Card. It is the question that cracks the hardest cases.&lt;/p&gt;

&lt;p&gt;Numbers from our Savanna (4.2.5, free tier) run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load:&lt;/strong&gt; 590,742 transactions, 202,440 underlying accounts, 9,705 device profiles, 5,565 closed cases and about 3.3M edges, posted to a GSQL loading job in 50k-line chunks in about 6 minutes, every line valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries:&lt;/strong&gt; 20 installed queries (17 graph tools plus 3 vector searches); the graph tools answer in about 100 ms each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP:&lt;/strong&gt; the same installed queries also run through the TigerGraph MCP server, and the agent's decisions are identical on both paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TigerVector:&lt;/strong&gt; 384-d embeddings of every closed-case analyst note and of the policy text live on the vertices; &lt;code&gt;vectorSearch()&lt;/code&gt; finds, for a sub-$500 burst, exactly the closed cases the analysts labelled undocumented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case memory:&lt;/strong&gt; every investigation is written back as an &lt;code&gt;InvestigationCase&lt;/code&gt; vertex with edges and an embedding, read back before we mark it written, and found again by the next investigation on the same card.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two things tripped us. &lt;code&gt;proxy&lt;/code&gt; is a reserved word in attribute lists, but only when another attribute follows it. And a fresh workspace came with a sample solution whose global &lt;code&gt;Card&lt;/code&gt; type collided with ours, so we build our graph with graph-local types in a schema change job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data taught us
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A card id is not a person.&lt;/strong&gt; &lt;code&gt;customer_id&lt;/code&gt; is derived from the card issuer field, so a single card id can pool thousands of real accounts; one of ours had 10,332 transactions across dozens of regions. "This region is familiar to the card" means nothing on such a card. We rebuilt the underlying account as &lt;code&gt;card1 | billing region | account start day&lt;/code&gt; (the start day comes from D1, days since the account opened) and moved every baseline to that account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The closed cases are a nearly complete label set.&lt;/strong&gt; Confirmed-fraud transactions are 3.4% of July to October traffic. An account with an earlier confirmed-fraud case turned out to be fraud in 1,059 later cases and cleared in none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The same score means different things.&lt;/strong&gt; Calibrated on the closed months, a score above 0.8 is 97% fraud for product C without an identity record, and 9% for product R. Kavach reads the score through that calibration instead of at face value. That single change turned several "high score, looks scary" alerts into correct legitimate closes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Selection bias hides in the labels.&lt;/strong&gt; Every cleared closed case was a high-score false alarm (0.82 to 0.94). Compare fraud with cleared naively and a new device looks &lt;em&gt;exculpatory&lt;/em&gt;. We fit every signal's likelihood ratio within each product, against both the whole population and the cleared cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Other cards solve cases.&lt;/strong&gt; A device profile seen on only 5 cards in six months paid about $100 on five customers' cards in six days. Nothing on any single card looks wrong. In the closed months that shape was 7 to 11 times more common in fraud than in normal traffic, and Kavach links the cards and recommends monitoring them under R6.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Some fraud has no name.&lt;/strong&gt; Two patterns fit none of the five documented typologies: four online purchases in 30 minutes, each priced just under $500, repeated across a dozen cards; and a scripted phone profile, always new to the account and always behind an anonymous proxy, buying small amounts on 28 cards. Kavach labels both &lt;code&gt;undocumented&lt;/code&gt;, describes them in its own words, and files a report under R9.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Eleven cases came out as fraud, eight as legitimate and one as uncertain (escalated with the evidence), and all 20 files pass our validator. Every investigation is in TigerGraph.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;p&lt;/th&gt;
&lt;th&gt;Exposure&lt;/th&gt;
&lt;th&gt;SAR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HHG-001&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.06&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-002&lt;/td&gt;
&lt;td&gt;uncertain&lt;/td&gt;
&lt;td&gt;card_not_present_fraud&lt;/td&gt;
&lt;td&gt;0.53&lt;/td&gt;
&lt;td&gt;$292.36&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-003&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;out_of_region_use&lt;/td&gt;
&lt;td&gt;0.93&lt;/td&gt;
&lt;td&gt;$165.93&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-004&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;card_not_present_new_device&lt;/td&gt;
&lt;td&gt;0.88&lt;/td&gt;
&lt;td&gt;$128.33&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-005&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.09&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-006&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;undocumented&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;td&gt;$1,906.07&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-007&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;account_takeover&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;td&gt;$228.88&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-008&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;card_not_present_fraud&lt;/td&gt;
&lt;td&gt;0.93&lt;/td&gt;
&lt;td&gt;$166.97&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-009&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;card_not_present_fraud&lt;/td&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;$30.02&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-010&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-011&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;card_not_present_new_device&lt;/td&gt;
&lt;td&gt;0.96&lt;/td&gt;
&lt;td&gt;$131.30&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-012&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-013&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-014&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;undocumented&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;td&gt;$439.61&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-015&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-016&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;card_not_present_new_device&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;$59.67&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-017&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.04&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-018&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;out_of_region_use&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;td&gt;$124.08&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-019&lt;/td&gt;
&lt;td&gt;fraud&lt;/td&gt;
&lt;td&gt;card_not_present_new_device&lt;/td&gt;
&lt;td&gt;0.96&lt;/td&gt;
&lt;td&gt;$99.92&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HHG-020&lt;/td&gt;
&lt;td&gt;legitimate&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Knowing when to ask
&lt;/h2&gt;

&lt;p&gt;When the evidence is short of a decision, Kavach does what the policy says: it verifies before blocking (R1), opens a case (section 3a), and simulates the customer's reply from the evidence it gathered &lt;em&gt;without&lt;/em&gt; the customer. Fraud-leaning evidence leads to a denial, legitimate-leaning evidence to a confirmation, and balanced evidence to no reply within 24 hours (R4, then escalation under R8). Every answer records the initial actions, the assumed reply, the final actions, and what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hand-investigate before you automate. We wrote 20 SQL dossiers before any agent code, and almost every detector traces back to one of them.&lt;/li&gt;
&lt;li&gt;Measure every weight. Three of our intuitions were wrong: new devices, high scores, and region familiarity.&lt;/li&gt;
&lt;li&gt;Keep the LLM on prose. It is very good at writing a FinCEN-style narrative from facts, and it should never be the thing deciding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Code: &lt;a href="https://github.com/MONSTERBOY110/hhgoa-tigergraph" rel="noopener noreferrer"&gt;https://github.com/MONSTERBOY110/hhgoa-tigergraph&lt;/a&gt;. Built for TigerGraph x Hacker House Goa 2026. Thanks to @TigerGraphDB and @247pmstudio.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tigergraph</category>
      <category>ai</category>
      <category>graphdatabase</category>
      <category>fraud</category>
    </item>
  </channel>
</rss>
