<?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: 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>
