<?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: Jyotier Vithalani</title>
    <description>The latest articles on DEV Community by Jyotier Vithalani (@jyotier2006).</description>
    <link>https://dev.to/jyotier2006</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%2F4141463%2Fe45ea5fc-93d0-4c0c-9508-23a01758a0fe.png</url>
      <title>DEV Community: Jyotier Vithalani</title>
      <link>https://dev.to/jyotier2006</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jyotier2006"/>
    <language>en</language>
    <item>
      <title>The fraud the score can't see: building an agentic investigator on TigerGraph</title>
      <dc:creator>Jyotier Vithalani</dc:creator>
      <pubDate>Thu, 24 Sep 2026 14:48:53 +0000</pubDate>
      <link>https://dev.to/jyotier2006/the-fraud-the-score-cant-see-building-an-agentic-investigator-on-tigergraph-3j99</link>
      <guid>https://dev.to/jyotier2006/the-fraud-the-score-cant-see-building-an-agentic-investigator-on-tigergraph-3j99</guid>
      <description>&lt;p&gt;dataset — 590,742 transactions, 5,565 closed investigations, 20 exam cases.*&lt;/p&gt;





&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Jyotier2006" rel="noopener noreferrer"&gt;
        Jyotier2006
      &lt;/a&gt; / &lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent" rel="noopener noreferrer"&gt;
        tigergraph-fraud-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Agentic fraud investigation on TigerGraph — graph-grounded evidence, calibrated probabilities, policy-as-code. Built for Hacker House Goa 2026.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Agentic Fraud Investigation on TigerGraph&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An investigation agent that works a fraud alert the way an analyst does —
traverses the graph, weighs the bank's own closed cases as memory, says how
certain it is, asks for more evidence when the policy demands it, and
recommends a defensible action with the approval route attached.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built for the TigerGraph × Hacker House Goa challenge on the HHGOA / IEEE-CIS dataset.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://savanna.tgcloud.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4a1fd5f7f377083ee27d7b8dccc8ef8ffc44af01090be9b204c92b0d1b783a4f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f546967657247726170682d342e322e352d6f72616e6765" alt="TigerGraph"&gt;&lt;/a&gt;
&lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent/gsql/03_queries.gsql" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/97379b2f4c3119de3d60f1e19a48cb6459416f19d47ed6543a1d6a17a4f2339b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4753514c2d3131253230696e7374616c6c6564253230717565726965732d6f72616e6765" alt="GSQL"&gt;&lt;/a&gt;
&lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent/agent/weights.json" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4195e7b5883d5463f4213cb4cf2bac8c1ccbbf6f8c05daa6e9c73bd3c7044a16/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f686f6c646f75742532304155432d302e3931352d626c7565" alt="Holdout AUC"&gt;&lt;/a&gt;
&lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent/cases/" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/af9fafed72c22d616e391c212815f31ba95852cb9708c7f0d9faa3dc9ff3c06a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f3230253246323025323063617365732d76616c696461746564253230302532306572726f72732d627269676874677265656e" alt="Answer files"&gt;&lt;/a&gt;
&lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent/requirements.txt" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/830da880f4d182316517c621c090de55b5fdc92a4698fd5d4aaeffe55ec924d8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31312d626c7565" alt="Python"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent#the-finding" rel="noopener noreferrer"&gt;The finding&lt;/a&gt; · &lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent#results-on-the-20-exam-cases" rel="noopener noreferrer"&gt;Results&lt;/a&gt; · &lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent#how-it-works" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; · &lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent#run-it" rel="noopener noreferrer"&gt;Run it&lt;/a&gt; · &lt;a href="https://github.com/Jyotier2006/tigergraph-fraud-agent#three-things-that-were-harder-than-they-looked" rel="noopener noreferrer"&gt;What we got wrong first&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem with a good fraud model&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every transaction in this dataset carries a risk score from the bank's detection
model. The obvious agent reads the score, looks at the transaction, decides.&lt;/p&gt;
&lt;p&gt;That agent fails — and the dataset is built to show you why. Pull the score
distribution out of the bank's own 5,565 closed investigations:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cleared (false alarm)&lt;/th&gt;
&lt;th&gt;confirmed fraud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;count&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Jyotier2006/tigergraph-fraud-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The problem with a good fraud model
&lt;/h2&gt;

&lt;p&gt;Every transaction in this dataset carries a risk score from the bank's detection&lt;br&gt;
model. The obvious agent reads the score, looks at the transaction, and decides.&lt;/p&gt;

&lt;p&gt;That agent fails, and the dataset is built to show you why. I pulled the score&lt;br&gt;
distribution out of the bank's own closed investigations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;cleared (false alarm)&lt;/th&gt;
&lt;th&gt;confirmed fraud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;count&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;4,665&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;median risk score&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.88&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.47&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;below 0.30&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,477&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the last row again. Not one false alarm scored below 0.30 — and 1,477&lt;br&gt;
confirmed frauds did. A high score means "somebody should look at this," and&lt;br&gt;
almost nothing more.&lt;/p&gt;

&lt;p&gt;So the interesting question isn't "is the score high?" It's &lt;strong&gt;what does this&lt;br&gt;
transaction look like next to everything else that touched the same card, the&lt;br&gt;
same device, the same region&lt;/strong&gt; — which is a graph question.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;An investigation loop, not a classifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger → anchor → baseline → window → spread → memory
                                                  ↓
                                   assess (calibrated + typologies)
                                          ↓
                          enough evidence? ──no──→ request evidence
                                          │                 │
                                         yes ←── re-assess ──┘
                                          ↓
                          decide + explain → write case to graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nine installed GSQL queries are the agent's tools. Every claim it makes carries&lt;br&gt;
the query that produced it, so an analyst can re-run the evidence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Device profile 'SM-G935F Build/NRD90M | Android 7.0 | chrome 62.0
            for android | 1920x1080' carries 28 distinct cards across unrelated
            customers inside a 30-day window (52 cards on this profile in the
            whole dataset, 54% of them in this window). Transactions cluster on
            product code C and the bank's model scored them a median of 0.13,
            so the cluster is invisible to the score."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"graph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"query:device_neighbors(device_key='SM-G935F Build/...', days=30)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The finding
&lt;/h2&gt;

&lt;p&gt;Case HHG-014 arrived as an analyst's hunch: &lt;em&gt;"several cards this month show&lt;br&gt;
purchases from the same unusual device profile."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Traversing out of the flagged transaction into its device profile and back down&lt;br&gt;
to that profile's other cards found 28 different cards, belonging to 28&lt;br&gt;
unrelated customers, inside 30 days. Every transaction product code &lt;code&gt;C&lt;/code&gt;. Median&lt;br&gt;
risk score &lt;strong&gt;0.13&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;None of it would ever be flagged. Each payment is unremarkable on its own. The&lt;br&gt;
pattern exists only as a shape in the graph.&lt;/p&gt;

&lt;p&gt;It matches none of the five documented typologies, so the agent classifies it&lt;br&gt;
&lt;code&gt;undocumented&lt;/code&gt;, describes it in its own words, and files under rule R9. Then I&lt;br&gt;
checked the bank's own history — and found &lt;code&gt;CC-2649&lt;/code&gt;: confirmed fraud, pattern&lt;br&gt;
&lt;code&gt;undocumented&lt;/code&gt;, sitting on the same device profile. The analysts had seen this&lt;br&gt;
before and never gave it a name.&lt;/p&gt;

&lt;p&gt;Then I let the agent scan November and December on its own, with no alert to&lt;br&gt;
start from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;362 rings. 1,330 cards. $531,647 of transactions.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;1 of the 362&lt;/strong&gt; had a median model score above 0.7.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Three things that were harder than they looked
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Most "shared devices" are not shared devices
&lt;/h3&gt;

&lt;p&gt;A device profile here is &lt;code&gt;DeviceInfo | OS | browser | screen&lt;/code&gt;. My first&lt;br&gt;
shared-origin detector fired on almost every case, which felt great until I&lt;br&gt;
counted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Windows | Windows 10 | chrome 63.0 | 1920x1080   →  842 distinct cards
unknown-device | unknown-os | unknown-browser     → 1011 distinct cards
SM-G935F Build/NRD90M | Android 7.0 | ...         →   52 distinct cards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first two aren't machines, they're browser-family buckets. Treating them as&lt;br&gt;
links connects every alert to hundreds of irrelevant cards and irrelevant closed&lt;br&gt;
cases.&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;DeviceProfile&lt;/code&gt; vertex carries &lt;code&gt;n_cards_total&lt;/code&gt; and &lt;code&gt;is_specific&lt;/code&gt;, and a&lt;br&gt;
shared-origin finding requires the profile to be &lt;strong&gt;rare&lt;/strong&gt; (≤ 60 lifetime cards)&lt;br&gt;
&lt;em&gt;and&lt;/em&gt; &lt;strong&gt;concentrated&lt;/strong&gt; (≥ 45% of its lifetime cards inside the window). That one&lt;br&gt;
distinction is the entire difference between finding the ring and drowning.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The labelled history has a selection bias, and it will happily teach you the wrong thing
&lt;/h3&gt;

&lt;p&gt;I calibrated &lt;code&gt;fraud_probability&lt;/code&gt; by replaying all 5,565 closed investigations&lt;br&gt;
through the same tools and detectors the agent runs live, then fitting a&lt;br&gt;
logistic model.&lt;/p&gt;

&lt;p&gt;First fit: &lt;strong&gt;AUC 0.983&lt;/strong&gt;. Suspiciously good — and the dominant coefficient was&lt;br&gt;
&lt;code&gt;risk_score&lt;/code&gt; at &lt;strong&gt;−9.5&lt;/strong&gt;. The model had learned &lt;em&gt;high score ⇒ legitimate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's true in the closed-case file, but only because the bank never opened a&lt;br&gt;
false-alarm case on a low-scoring alert (see the table above: zero cleared cases&lt;br&gt;
under 0.30). It's an artifact of which investigations got opened, not a property&lt;br&gt;
of fraud. An agent trained on it would clear every high-scoring alert.&lt;/p&gt;

&lt;p&gt;Fix: restrict training to the alert band (score ≥ 0.5), where the real prior is&lt;br&gt;
57% fraud / 43% cleared, and drop the raw score from the feature set. Result:&lt;br&gt;
&lt;strong&gt;AUC 0.915, Brier 0.109&lt;/strong&gt; — believable, and it doesn't invert.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. A learned feature can flip meaning between regimes
&lt;/h3&gt;

&lt;p&gt;In the refit, the strongest &lt;em&gt;exonerating&lt;/em&gt; feature was "identity record marks the&lt;br&gt;
device New" at −3.8. That is genuinely what the history says: most false alarms&lt;br&gt;
are somebody buying a phone. The dataset README even warns you — &lt;em&gt;"people buy&lt;br&gt;
new phones."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But those are all &lt;strong&gt;model-triggered&lt;/strong&gt; alerts. When the trigger is the cardholder&lt;br&gt;
phoning to say &lt;em&gt;"I never made this purchase,"&lt;/em&gt; a device that's new to the account&lt;br&gt;
stops being the innocent explanation and starts being corroboration.&lt;/p&gt;

&lt;p&gt;Same feature, opposite sign, depending on who raised the alert. So the learned&lt;br&gt;
coefficients are applied in the regime they were learned in, and a short&lt;br&gt;
suppression list handles disputes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SUPPRESS_FOR_DISPUTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device_status_new&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hist_thin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;night_hour&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_ratio_log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_over_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things history &lt;em&gt;can't&lt;/em&gt; teach — card testing has 16 examples, the device ring is&lt;br&gt;
undocumented by definition — are applied as explicit, cited overrides rather than&lt;br&gt;
dressed up as learned.&lt;/p&gt;
&lt;h2&gt;
  
  
  Policy as code, not as prompt
&lt;/h2&gt;

&lt;p&gt;Actions, the approval routing table and rules R1–R10 live in &lt;code&gt;policy.py&lt;/code&gt;, not in&lt;br&gt;
a system prompt. The agent cannot invent an action or mis-route an approval, and&lt;br&gt;
every recommendation names the rule that produced it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exposure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;AUTO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK_CARD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;L1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exposure&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;L2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK_ALL_CARDS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FILE_REPORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;L2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BLOCK_CARD&lt;/code&gt; at $2,400 routes to a team lead; at $2,600, to a fraud manager.&lt;br&gt;
That's not a thing to leave to sampling temperature.&lt;/p&gt;

&lt;p&gt;The LLM's job is what it's good at — reading the retrieved policy text and the&lt;br&gt;
analyst narratives, and writing the case summary and the SAR. The decision&lt;br&gt;
itself is graph evidence plus a calibrated model plus an explicit rule.&lt;/p&gt;
&lt;h2&gt;
  
  
  Uncertainty is the product
&lt;/h2&gt;

&lt;p&gt;Rule R1: on a single signal below 0.70, verify before you block. So the agent&lt;br&gt;
records both recommendations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial:  VERIFY_WITH_CUSTOMER (auto) · MONITOR_CARD (auto)
final:    BLOCK_CARD (L1) · CREATE_CASE (auto) · FILE_REPORT (L2)
changed:  the assumed response moved probability from 0.46 to 0.75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customer replies aren't provided in this round, so they're simulated &lt;em&gt;from the&lt;br&gt;
independent graph evidence&lt;/em&gt; — never from the conclusion — and the assumption is&lt;br&gt;
written into &lt;code&gt;evidence_requests&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Across the 20 exam cases: &lt;strong&gt;10 fraud, 8 legitimate, 2 uncertain&lt;/strong&gt;, 3 reports&lt;br&gt;
filed. Eight clean closes is the result I wanted. The dataset warns that half the&lt;br&gt;
cases are legitimate and that an agent which blocks everything scores badly; the&lt;br&gt;
expensive failure in fraud isn't a missed alert, it's blocking a cardholder at a&lt;br&gt;
checkout because one model said 0.87.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do with more time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vector search over the analyst narratives.&lt;/strong&gt; I used TF-IDF, which is
dependency-free and works, but the closed-case notes are exactly the kind of
text embeddings are good at. TigerGraph's vector store would replace it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learn the ring detector instead of specifying it.&lt;/strong&gt; The rarity and
concentration thresholds (60 cards, 45%) came from looking at the distribution.
With labelled rings they could be fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the second hop cheaper.&lt;/strong&gt; Every investigation re-derives the device
cohort. Materialising a card↔card "shared rare device" edge at load time would
turn the expensive traversal into a one-hop lookup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed the agent's own cases back in properly.&lt;/strong&gt; Each case is written back as
an &lt;code&gt;AgentCase&lt;/code&gt; vertex wired to its transactions, cards, device and cited priors,
and &lt;code&gt;case_memory&lt;/code&gt; reads them. What I haven't done is run the pack &lt;em&gt;twice&lt;/em&gt; and
measure whether the second pass is better — which is the only honest test of
whether the memory is worth anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting this
&lt;/h2&gt;

&lt;p&gt;Spend the first hour on the data, not the agent. Everything that made this work —&lt;br&gt;
the device-profile rarity, the selection bias in the labels, the feature that&lt;br&gt;
flips sign between triggers — came from counting things before writing the&lt;br&gt;
investigation loop. The agent is the easy part; knowing what makes a link&lt;br&gt;
&lt;em&gt;evidence&lt;/em&gt; is the whole job.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code, the 20 case files, the analyst console and the autonomous ring scan are in&lt;br&gt;
the repository.&lt;/em&gt;&lt;/p&gt;

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