Rohit Patil commited on
Commit
753201e
Β·
0 Parent(s):

RingGuard Phase 3 - initial HF Spaces deploy

Browse files

Graph-based fraud ring detection for Razorpay merchants.
FastAPI dashboard serving precomputed detection results.

Stack: Python 3.11, FastAPI, LightGBM, LangGraph, NetworkX, Groq, Razorpay SDK

.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Not required for Phase 1. Needed starting Phase 2/3.
2
+ GROQ_API_KEY=
3
+ RAZORPAY_KEY_ID=
4
+ RAZORPAY_KEY_SECRET=
.gitignore ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ο»Ώvenv/
2
+ __pycache__/
3
+ *.pyc
4
+ .env
5
+ .DS_Store
6
+ data/synthetic/graph.gpickle
7
+ data/synthetic_train/graph.gpickle
8
+
9
+ # Phase execution plans and summaries - keep local only, not in public repo
10
+ phase-1-execution-plan.md
11
+ phase-2-execution-plan.md
12
+ phase-3-execution-plan.md
13
+ phase1_summary.md
14
+ phase2_summary.md
15
+ phase3_summary.md
16
+ data/synthetic_train/
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Phase 3 deployment β€” RingGuard FastAPI dashboard
4
+ # Razorpay actions are precomputed and baked into cases_demo.json / audit_log.jsonl.
5
+ # The deployed container only DISPLAYS these precomputed results; it does NOT
6
+ # re-execute Razorpay calls or LLM calls on page load (per Phase 3 Section 6.2).
7
+
8
+ WORKDIR /app
9
+
10
+ # Install dependencies
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy source code and precomputed results
15
+ COPY agent/ agent/
16
+ COPY api/ api/
17
+ COPY audit/ audit/
18
+ COPY data/ data/
19
+ COPY graph/ graph/
20
+ COPY integration/ integration/
21
+ COPY scoring/ scoring/
22
+ COPY eval/ eval/
23
+
24
+ # HuggingFace Spaces expects port 7860
25
+ ENV PORT=7860
26
+ EXPOSE 7860
27
+
28
+ # Start the FastAPI dashboard
29
+ CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "7860"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rohit Patil
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ο»Ώ---
2
+ title: RingGuard
3
+ colorFrom: red
4
+ colorTo: orange
5
+ sdk: docker
6
+ pinned: true
7
+ license: mit
8
+ short_description: Graph-based fraud ring detection for Razorpay merchants
9
+ ---
10
+ # RingGuard
11
+
12
+ ![Track](https://img.shields.io/badge/Track-AI%20Risk%20Manager-blue) ![Event](https://img.shields.io/badge/Event-Razorpay%20AI%20Buildathon%202026-orange) ![Status](https://img.shields.io/badge/Status-Phase%203%20Complete-brightgreen) ![Python](https://img.shields.io/badge/Python-3.11-blue) ![License](https://img.shields.io/badge/License-MIT-green)
13
+
14
+ **Coordinated fraud detection for Razorpay merchants Ò€” finding the rings per-order scoring can't see.**
15
+
16
+ RingGuard analyzes a merchant's own order and account data to detect coordinated abuse Ò€” accounts secretly linked by shared devices, addresses, or payment instruments, working together to exploit returns, promo codes, or chargebacks. Instead of scoring one order at a time, it builds a graph of the merchant's entire transaction history, finds the structure a per-order model is blind to, and hands a human reviewer an explainable case file instead of a black-box flag. Built for Track 02 Ò€” AI Risk Manager.
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ 1. [The Problem](#the-problem)
23
+ 2. [The Solution](#the-solution)
24
+ 3. [Why This, Not Thirdwatch](#why-this-not-thirdwatch)
25
+ 4. [How It Works](#how-it-works)
26
+ 5. [AI Judgment Ò€” Where Each Tool Is Used](#ai-judgment--where-each-tool-is-used)
27
+ 6. [System Architecture](#system-architecture)
28
+ 7. [Tech Stack](#tech-stack)
29
+ 8. [Repository Structure](#repository-structure)
30
+ 9. [Getting Started](#getting-started)
31
+ 10. [Usage](#usage)
32
+ 11. [Evaluation & Metrics](#evaluation--metrics)
33
+ 12. [Known Limitations](#known-limitations)
34
+ 13. [Policy & Safety Boundaries](#policy--safety-boundaries)
35
+ 14. [Audit Trail](#audit-trail)
36
+ 15. [Roadmap](#roadmap)
37
+ 16. [Demo](#demo)
38
+ 17. [Acknowledgments](#acknowledgments)
39
+ 18. [License](#license)
40
+
41
+ ---
42
+
43
+ ## The Problem
44
+
45
+ Cash-on-delivery return-to-origin rates in Indian e-commerce run as high as 30Γ’β‚¬β€œ40%, and coordinated abuse Ò€” not isolated bad actors Ò€” is a meaningful share of that loss. Fraud rings deliberately spread activity across multiple "different" accounts specifically so that any system scoring one order at a time sees N separate low-risk transactions instead of one high-risk network. Per-order risk engines, however sophisticated, are structurally blind to this by design Ò€” they were never built to look across accounts.
46
+
47
+ ## The Solution
48
+
49
+ RingGuard is a defensive, AI-assisted investigation agent that:
50
+
51
+ - **Ingests** a merchant's order, customer, device, address, and payment data (Razorpay test-mode-shaped).
52
+ - **Builds a graph** connecting accounts through shared devices, payment instruments, addresses, and contact details.
53
+ - **Detects communities** in that graph Ò€” clusters of accounts more connected to each other than chance would suggest.
54
+ - **Scores each cluster** with a trained model, not hand-tuned rules, using graph structure and behavioral features.
55
+ - **Explains the finding** in plain language via an LLM that reads only the structured evidence it's given Ò€” never free-reasoning, never inventing facts.
56
+ - **Recommends a bounded action** Ò€” flag, hold, or escalate Ò€” gated by deterministic policy rules, with a human always in the loop for anything above the auto-hold threshold.
57
+
58
+ It never bans an account or seizes funds. It flags, it explains, and it waits for a human on anything that matters.
59
+
60
+ ## Why This, Not Thirdwatch
61
+
62
+ Razorpay has owned Thirdwatch since 2019 Ò€” a mature fraud/RTO engine scoring individual orders on roughly 300 device and behavioral signals. Razorpay's Agent Studio also ships a Dispute Responder Agent (chargeback evidence) and a Subscription Recovery Agent. None of these look *across* orders and accounts for structure Ò€” they operate at the level of one order or one case at a time.
63
+
64
+ RingGuard is not a better per-order scorer. It's a different problem shape: network-level detection of coordinated behavior that is invisible when you only ever look at one order at a time. That's the entire differentiation argument, and it's intentional that it's stated here, first, rather than left for a judge to have to ask about.
65
+
66
+ ## How It Works
67
+
68
+ ```
69
+ Synthetic order/account stream (Razorpay Orders/Payments-shaped)
70
+ Γ’β€β€š
71
+ Ò–¼
72
+ Entity resolution Ò€” normalize phone/email/address, hash device + payment fields
73
+ Γ’β€β€š
74
+ Ò–¼
75
+ Graph construction Ò€” Customer / Device / Address / PaymentInstrument nodes
76
+ Γ’β€β€š
77
+ Ò–¼
78
+ Community detection Ò€” connected components + Louvain (deterministic, seed=42)
79
+ Γ’β€β€š
80
+ Ò–¼
81
+ Feature extraction Ò€” cluster size, strong/weak link ratio, order velocity,
82
+ return rate, promo usage, Γ’β€šΒΉ exposure (13 features)
83
+ Γ’β€β€š
84
+ Ò–¼
85
+ LightGBM risk model Ò€” single learned probability, no hand-blended scoring
86
+ Γ’β€β€š
87
+ Ò–¼
88
+ Policy gate (deterministic, v3.0)
89
+ Ò”œÒ”€Ò”€ risk Ò‰Β₯ 0.85 and exposure Ò‰€ Γ’β€šΒΉ25,000 Ò†’ bounded auto-hold (test mode, Razorpay API)
90
+ Ò”œÒ”€Ò”€ risk Ò‰Β₯ 0.85 and exposure > Γ’β€šΒΉ25,000 Ò†’ escalate, no auto-action
91
+ Ò”œÒ”€Ò”€ 0.5 Ò‰€ risk < 0.85 Ò†’ escalate to human review queue
92
+ Ò””Ò”€Ò”€ risk < 0.5 Ò†’ log only
93
+ Γ’β€β€š
94
+ Ò–¼
95
+ Investigator agent (LangGraph + Groq LLM) Ò€” evidence bundle in, case narrative out
96
+ Γ’β€β€š
97
+ Ò–¼
98
+ Razorpay test-mode action (order.create + order.edit notes)
99
+ Γ’β€β€š
100
+ Ò–¼
101
+ Audit logger Ò€” every stage, every decision, timestamped (audit_log.jsonl)
102
+ ```
103
+
104
+ A cluster built entirely from **weak links** (e.g., three accounts sharing only an address Ò€” plausible for a family household) is designed to score low. Distinguishing that from a **strong-link** cluster (shared device fingerprint or payment instrument) is what keeps false positives down, and it's built into the feature set from day one, not patched in afterward.
105
+
106
+ ## AI Judgment Ò€” Where Each Tool Is Used
107
+
108
+ | Stage | Tool | Why this tool, not another |
109
+ |---|---|---|
110
+ | Graph construction, edge rules, policy gate | Deterministic code | Money-adjacent decisions need to be boring and auditable Ò€” no model judgment here |
111
+ | Community detection, clustering | Classical graph algorithms (Louvain, connected components) | Structural pattern detection, not a language problem |
112
+ | Ring risk scoring | LightGBM (supervised, tabular) | Faster, cheaper, and more auditable than an LLM for a numeric classification task |
113
+ | Case narrative + human-readable explanation | LLM (Groq Ò€” `openai/gpt-oss-120b`) | The one place synthesizing evidence into something a reviewer can act on quickly genuinely benefits from language generation |
114
+ | Final action | Deterministic policy gate, human-approved above threshold | The LLM never has authority to move money or take action on its own |
115
+
116
+ ## System Architecture
117
+
118
+ ```mermaid
119
+ flowchart TD
120
+ A[Synthetic Order Stream] --> B[Entity Resolution]
121
+ B --> C[Graph Construction - NetworkX]
122
+ C --> D[Community Detection - Louvain]
123
+ D --> E[Feature Extraction]
124
+ E --> F[LightGBM Risk Model]
125
+ F --> G{Policy Gate}
126
+ G -->|risk >= 0.85, exposure <= 25k| H[Auto-Hold via Razorpay API]
127
+ G -->|risk >= 0.85, exposure > 25k| I[Escalate - Human Review]
128
+ G -->|0.5 <= risk < 0.85| I
129
+ G -->|risk < 0.5| J[Log Only]
130
+ H --> K[Investigator Agent - Groq LLM]
131
+ I --> K
132
+ K --> L[Case Narrative + Confidence]
133
+ L --> M[Audit Log + Dashboard]
134
+ J --> M
135
+ ```
136
+
137
+ ## Tech Stack
138
+
139
+ | Layer | Choice |
140
+ |---|---|
141
+ | Language / API | Python 3.11, FastAPI |
142
+ | Graph | NetworkX + python-louvain |
143
+ | Scoring | LightGBM |
144
+ | Agent orchestration | LangGraph |
145
+ | LLM | Groq (`openai/gpt-oss-120b`, plain chat completion Ò€” no tools) |
146
+ | Razorpay integration | Official Razorpay Python SDK v2.0.1 (test-mode API calls verified) |
147
+ | Synthetic data | Faker |
148
+ | Dashboard | HTML served by FastAPI |
149
+ | Deployment | HuggingFace Spaces (Docker) |
150
+
151
+ No paid dependencies anywhere in this stack.
152
+
153
+ ## Repository Structure
154
+
155
+ ```
156
+ ringguard/
157
+ Ò”œÒ”€Ò”€ README.md
158
+ Ò”œÒ”€Ò”€ requirements.txt
159
+ Ò”œÒ”€Ò”€ .env.example
160
+ Ò”œÒ”€Ò”€ Dockerfile
161
+ Ò”œÒ”€Ò”€ data/
162
+ Γ’β€β€š Ò”œÒ”€Ò”€ generate_synthetic_data.py
163
+ Γ’β€β€š Ò”œÒ”€Ò”€ augment_demo_dataset.py Ò† Phase 3: adds RING11 + BOUNDARY01
164
+ Γ’β€β€š Ò””Ò”€Ò”€ synthetic/
165
+ Γ’β€β€š Ò”œÒ”€Ò”€ cases_demo.json Ò† 28 cases with Razorpay action_taken
166
+ Γ’β€β€š Ò””Ò”€Ò”€ ...
167
+ Ò”œÒ”€Ò”€ graph/
168
+ Γ’β€β€š Ò”œÒ”€Ò”€ build_graph.py
169
+ Γ’β€β€š Ò””Ò”€Ò”€ detect_communities.py
170
+ Ò”œÒ”€Ò”€ scoring/
171
+ Γ’β€β€š Ò”œÒ”€Ò”€ features.py
172
+ Γ’β€β€š Ò”œÒ”€Ò”€ train_model.py
173
+ Γ’β€β€š Ò”œÒ”€Ò”€ score_demo.py
174
+ Γ’β€β€š Ò””Ò”€Ò”€ model.lgb
175
+ Ò”œÒ”€Ò”€ agent/
176
+ Γ’β€β€š Ò”œÒ”€Ò”€ graph.py
177
+ Γ’β€β€š Ò”œÒ”€Ò”€ investigator.py
178
+ Γ’β€β€š Ò”œÒ”€Ò”€ policy.py
179
+ Γ’β€β€š Ò””Ò”€Ò”€ run_demo.py
180
+ Ò”œÒ”€Ò”€ integration/
181
+ Γ’β€β€š Ò””Ò”€Ò”€ razorpay_actions.py Ò† Phase 3: real test-mode Razorpay calls
182
+ Ò”œÒ”€Ò”€ api/
183
+ Γ’β€β€š Ò””Ò”€Ò”€ main.py Ò† Phase 3: FastAPI dashboard
184
+ Ò”œÒ”€Ò”€ eval/
185
+ Γ’β€β€š Ò””Ò”€Ò”€ evaluate.py
186
+ Ò””Ò”€Ò”€ audit/
187
+ Ò”œÒ”€Ò”€ logger.py Ò† Phase 3: append-only audit log
188
+ Ò”œÒ”€Ò”€ generate_audit_log.py
189
+ Ò””Ò”€Ò”€ audit_log.jsonl
190
+ ```
191
+
192
+ ## Getting Started
193
+
194
+ ### Prerequisites
195
+
196
+ - Python 3.11+
197
+ - A free [Groq API key](https://console.groq.com)
198
+ - Razorpay test-mode API keys (`rzp_test_...`) from the [Razorpay Dashboard](https://dashboard.razorpay.com)
199
+
200
+ ### Installation
201
+
202
+ ```bash
203
+ git clone https://github.com/rohitdecodes/ringguard.git
204
+ cd ringguard
205
+ python -m venv venv
206
+ source venv/bin/activate # Windows: venv\Scripts\activate
207
+ pip install -r requirements.txt
208
+ cp .env.example .env # then fill in GROQ_API_KEY, RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET
209
+ ```
210
+
211
+ ## Usage
212
+
213
+ ```bash
214
+ # 1. (One-time) Augment the demo dataset with RING11 + BOUNDARY01
215
+ python data/augment_demo_dataset.py
216
+
217
+ # 2. Build the graph and detect candidate clusters
218
+ python graph/build_graph.py --data-dir data/synthetic
219
+ python graph/detect_communities.py --data-dir data/synthetic
220
+
221
+ # 3. Score clusters and run the LangGraph agent
222
+ python scoring/score_demo.py
223
+ python agent/run_demo.py
224
+
225
+ # 4. Execute Razorpay test-mode actions
226
+ python integration/razorpay_actions.py
227
+
228
+ # 5. Generate the audit log
229
+ python audit/generate_audit_log.py
230
+
231
+ # 6. Launch the dashboard
232
+ uvicorn api.main:app --reload --port 7860
233
+ # open http://localhost:7860
234
+ ```
235
+
236
+ ## Evaluation & Metrics
237
+
238
+ Measured on a held-out split at the **cluster level** (no ring's accounts appear in both train and test). Fixed seed=42, stratified 80/20 split.
239
+
240
+ | Metric | Value |
241
+ |---|---|
242
+ | **Precision** (ring detection) | **1.0000** |
243
+ | **Recall** (ring detection) | **1.0000** |
244
+ | **F1** | **1.0000** |
245
+ | False-positive cost (Γ’β€šΒΉ, held orders wrongly flagged) | **Γ’β€šΒΉ0.00** |
246
+ | End-to-end latency (feature extraction + inference) | **0.043 ms/cluster** |
247
+
248
+ Holdout: 22 clusters (12 positive fraud rings, 10 negative benign clusters).
249
+
250
+ ### Policy action breakdown on held-out set
251
+ | Action | Count |
252
+ |---|---|
253
+ | auto_hold | 0 |
254
+ | escalate | 12 |
255
+ | log_only | 10 |
256
+
257
+ ### Demo dataset (28 clusters, post Phase 3 augmentation)
258
+ | Action | Count |
259
+ |---|---|
260
+ | auto_hold | 1 (RING11) |
261
+ | escalate | 10 |
262
+ | log_only | 17 (includes BOUNDARY01) |
263
+
264
+ ## Known Limitations
265
+
266
+ **Read this before evaluating the precision/recall numbers above.**
267
+
268
+ The current P=R=F1=1.0 figures reflect that the synthetic training data does not yet contain genuinely ambiguous or partially-ring-like examples. The model has not been tested on a hard case Ò€” one where real human experts would disagree. Specifically:
269
+
270
+ 1. **Training distribution boundary:** The Phase 2 model uses `avg_order_amount` as its strongest feature (importance=145 vs. 120 for chargeback_rate). Positive training examples have `avg_order_amount Ò‰Β₯ Γ’β€šΒΉ3,249`; all negatives are below Γ’β€šΒΉ3,171. This creates a near-hard threshold. Real rings could fall on either side of it.
271
+
272
+ 2. **Auto-hold threshold calibration (Phase 3 change):** The original Γ’β€šΒΉ5,000 auto-hold exposure cap is mathematically incompatible with the training distribution Ò€” a 3-member ring with just enough orders to form strong links (2 per customer Γƒβ€” Γ’β€šΒΉ3,300 each) totals Γ’β€°Λ†Γ’β€šΒΉ19,800. The threshold was updated to Γ’β€šΒΉ25,000 in Phase 3 (v3.0). This is a legitimate calibration, but it means the original threshold was never validated against real data either.
273
+
274
+ 3. **Razorpay MCP server not available:** The `@razorpay/mcp-server` npm package returns 404 on the npm registry. Razorpay actions were executed via the official Python SDK (`razorpay==2.0.1`), which calls the same REST API. All calls are real, test-mode, and verified Ò€” but the MCP-specific tooling described in the original plan is not available.
275
+
276
+ 4. **Synthetic data only:** All evaluation is against Faker-generated data. Real merchants have messier entity resolution challenges, more diverse ring structures, and ambiguous cases the current training data cannot represent. Real-world P/R/F1 should be expected to be significantly lower.
277
+
278
+ 5. **`BOUNDARY01` detection behavior:** This deliberately ambiguous cluster (3 accounts sharing one address, no chargebacks, low return rate) scored `risk=0.0035` and was correctly routed to `log_only` Ò€” not `auto_hold`. The model correctly declined to flag it. However, this isn't a hard-won result from a genuinely difficult case; it's the model doing nothing to an input that looks entirely unlike its training positives.
279
+
280
+ Real-world performance validation requires labeled production data. The current metrics are a correctness proof-of-concept, not a deployment readiness certificate.
281
+
282
+ ## Policy & Safety Boundaries
283
+
284
+ RingGuard is **defense-only**, per Track 02's rules:
285
+
286
+ - It can flag, hold, and escalate. It cannot ban, seize funds, or take any irreversible action.
287
+ - Auto-hold only fires below Γ’β€šΒΉ25,000 total cluster exposure (Phase 3 v3.0 calibration) and only at high confidence (risk Ò‰Β₯ 0.85).
288
+ - Anything above that threshold, or in the mid-confidence band, goes to a human review queue Ò€” never auto-actioned.
289
+ - The LLM has no authority to trigger an action. It explains a decision the policy gate already made.
290
+ - The LLM (`openai/gpt-oss-120b`) is called as a plain chat completion with no `tools` parameter Ò€” it has no web-search or code-execution access. It only knows what's in the evidence bundle.
291
+
292
+ ## Audit Trail
293
+
294
+ Every case logs: `case_id, timestamp, cluster_members, evidence_bundle, graph_score_inputs, lightgbm_probability, llm_narrative, llm_confidence_statement, policy_decision, action_taken, human_reviewer (if escalated), outcome, reason_if_not_auto_actioned`.
295
+
296
+ Available as `audit/audit_log.jsonl` (one JSON object per line, append-only). Every case in `cases_demo.json` has a matching audit entry, including log_only cases. The dashboard at `GET /cases/{case_id}/detail` shows the full audit chain for each case.
297
+
298
+ ## Roadmap
299
+
300
+ Building in three phases. Detailed, task-level execution plans for each phase Ò€” written for agentic AI coding execution Ò€” are maintained separately.
301
+
302
+ - Òœ… **Phase 1 Ò€” Detection Core:** synthetic data generation, entity resolution, graph construction, community detection. Output: a working, deterministic pipeline that turns raw order data into flagged candidate clusters.
303
+ - Òœ… **Phase 2 Ò€” Intelligence Layer:** feature extraction, LightGBM risk model, held-out evaluation, LangGraph agent orchestration, Investigator LLM node, policy gate. Output: scored, explained, policy-gated cases.
304
+ - Òœ… **Phase 3 Ò€” Integration & Submission:** dataset augmentation (RING11 + BOUNDARY01), model swap (`openai/gpt-oss-120b`), Razorpay test-mode integration, audit trail, FastAPI dashboard, deployment, final evaluation, demo, submission.
305
+
306
+ ## Demo
307
+
308
+ *Live deployment: HuggingFace Spaces Ò€” see phase3_summary.md for URL.*
309
+
310
+ *Pitch video: to be recorded.*
311
+
312
+ ## Acknowledgments
313
+
314
+ Built with [LangGraph](https://langchain-ai.github.io/langgraph/), [Groq](https://groq.com) (`openai/gpt-oss-120b`), [LightGBM](https://lightgbm.readthedocs.io/), [NetworkX](https://networkx.org/), and the [Razorpay Python SDK](https://github.com/razorpay/razorpay-python).
315
+
316
+ ## License
317
+
318
+ MIT Ò€” see [LICENSE](LICENSE).
319
+
320
+
agent/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
agent/graph.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ graph.py
3
+ --------
4
+ LangGraph orchestration for RingGuard fraud investigation.
5
+
6
+ State: RingGuardState
7
+ cluster β€” the candidate cluster record
8
+ features β€” extracted feature dict
9
+ risk_probability β€” model score
10
+ policy_decision β€” "auto_hold" | "escalate" | "log_only"
11
+ llm_narrative β€” LLM explanation (None if log_only or if GROQ unavailable)
12
+ llm_confidence β€” LLM confidence statement (None if log_only)
13
+
14
+ Flow:
15
+ score_node β†’ policy_node β†’ [conditional] investigator_node β†’ END
16
+ (investigator_node is SKIPPED for log_only decisions)
17
+
18
+ Usage:
19
+ from agent.graph import build_graph, run_cluster
20
+ results = run_cluster(cluster, features, risk_probability)
21
+ """
22
+
23
+ import os
24
+ import sys
25
+ from typing import Optional, TypedDict
26
+
27
+ from langgraph.graph import StateGraph, END
28
+
29
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
30
+ sys.path.insert(0, BASE_DIR)
31
+
32
+ from agent.policy import decide as policy_decide
33
+ from agent.investigator import investigate
34
+
35
+ # Audit logger β€” imported lazily to keep graph.py usable without audit module
36
+ _AUDIT_ENABLED = True
37
+ try:
38
+ from audit.logger import log_case as _log_case
39
+ except ImportError:
40
+ _AUDIT_ENABLED = False
41
+ _log_case = None
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # State definition
46
+ # ---------------------------------------------------------------------------
47
+
48
+ class RingGuardState(TypedDict):
49
+ cluster: dict
50
+ features: dict
51
+ risk_probability: float
52
+ policy_decision: Optional[str]
53
+ llm_narrative: Optional[str]
54
+ llm_confidence: Optional[str]
55
+ grounding_ok: Optional[bool]
56
+
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # Nodes
60
+ # ---------------------------------------------------------------------------
61
+
62
+ def score_node(state: RingGuardState) -> RingGuardState:
63
+ """
64
+ Score node: wraps the already-computed risk_probability from the model.
65
+ The probability was computed externally (by scoring/score_demo.py or live
66
+ inference) and passed in via state β€” this node simply forwards it.
67
+ """
68
+ # risk_probability is already set in state by the caller
69
+ return state
70
+
71
+
72
+ def policy_node(state: RingGuardState) -> RingGuardState:
73
+ """
74
+ Policy gate: deterministic decision based on risk_probability + exposure.
75
+ Exposure = total_amount from features (Phase 3 will use real Razorpay data).
76
+ """
77
+ risk_prob = state["risk_probability"]
78
+ exposure = state["features"].get("total_amount", 0.0)
79
+ decision = policy_decide(risk_prob, exposure)
80
+
81
+ return {**state, "policy_decision": decision}
82
+
83
+
84
+ def investigator_node(state: RingGuardState) -> RingGuardState:
85
+ """
86
+ LLM investigator: generates a narrative for high-risk clusters.
87
+ Only called when policy_decision is 'auto_hold' or 'escalate'.
88
+ Catches errors so a single LLM failure does not abort the whole pipeline.
89
+ """
90
+ try:
91
+ result = investigate(
92
+ cluster=state["cluster"],
93
+ features=state["features"],
94
+ risk_probability=state["risk_probability"],
95
+ policy_decision=state["policy_decision"],
96
+ )
97
+ return {
98
+ **state,
99
+ "llm_narrative": result["narrative"],
100
+ "llm_confidence": result["confidence_statement"],
101
+ "grounding_ok": result["grounding_ok"],
102
+ }
103
+ except Exception as e:
104
+ return {
105
+ **state,
106
+ "llm_narrative": f"[LLM_ERROR] {e}",
107
+ "llm_confidence": None,
108
+ "grounding_ok": None,
109
+ }
110
+
111
+
112
+ # ---------------------------------------------------------------------------
113
+ # Conditional routing
114
+ # ---------------------------------------------------------------------------
115
+
116
+ def should_investigate(state: RingGuardState) -> str:
117
+ """
118
+ Route to investigator only for auto_hold or escalate decisions.
119
+ Skip for log_only β€” no reason to spend an LLM call on it.
120
+ """
121
+ if state.get("policy_decision") in ("auto_hold", "escalate"):
122
+ return "investigate"
123
+ return "skip"
124
+
125
+
126
+ # ---------------------------------------------------------------------------
127
+ # Graph construction
128
+ # ---------------------------------------------------------------------------
129
+
130
+ def build_graph():
131
+ """Build and compile the RingGuard LangGraph state machine."""
132
+ builder = StateGraph(RingGuardState)
133
+
134
+ builder.add_node("score_node", score_node)
135
+ builder.add_node("policy_node", policy_node)
136
+ builder.add_node("investigator_node", investigator_node)
137
+
138
+ builder.set_entry_point("score_node")
139
+
140
+ builder.add_edge("score_node", "policy_node")
141
+
142
+ builder.add_conditional_edges(
143
+ "policy_node",
144
+ should_investigate,
145
+ {
146
+ "investigate": "investigator_node",
147
+ "skip": END,
148
+ },
149
+ )
150
+
151
+ builder.add_edge("investigator_node", END)
152
+
153
+ return builder.compile()
154
+
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Convenience runner
158
+ # ---------------------------------------------------------------------------
159
+
160
+ def run_cluster(
161
+ cluster: dict,
162
+ features: dict,
163
+ risk_probability: float,
164
+ case_id: str = None,
165
+ action_taken: dict = None,
166
+ ) -> dict:
167
+ """
168
+ Run the full pipeline for a single cluster.
169
+
170
+ Returns final state dict with policy_decision, llm_narrative, etc.
171
+ Catches LLM errors and records them in llm_narrative rather than crashing.
172
+
173
+ Parameters
174
+ ----------
175
+ cluster : candidate cluster record
176
+ features : extracted feature dict
177
+ risk_probability : model score
178
+ case_id : optional case ID for audit logging (e.g. "CASE08")
179
+ action_taken : optional Razorpay action dict (populated by Section 3)
180
+ """
181
+ graph = build_graph()
182
+
183
+ initial_state: RingGuardState = {
184
+ "cluster": cluster,
185
+ "features": features,
186
+ "risk_probability": risk_probability,
187
+ "policy_decision": None,
188
+ "llm_narrative": None,
189
+ "llm_confidence": None,
190
+ "grounding_ok": None,
191
+ }
192
+
193
+ try:
194
+ final_state = graph.invoke(initial_state)
195
+ except RuntimeError as e:
196
+ # LLM failures (missing key, parse errors) are caught here.
197
+ # The policy decision was already set before the LLM call.
198
+ # We log the error rather than crashing the whole pipeline.
199
+ final_state = {
200
+ **initial_state,
201
+ "policy_decision": policy_decide(risk_probability, features.get("total_amount", 0.0)),
202
+ "llm_narrative": f"[LLM_ERROR] {e}",
203
+ "llm_confidence": None,
204
+ "grounding_ok": None,
205
+ }
206
+
207
+ result = dict(final_state)
208
+
209
+ # Phase 3 Section 4.2 β€” write audit log entry for every run
210
+ if _AUDIT_ENABLED and _log_case is not None:
211
+ try:
212
+ from datetime import datetime, timezone
213
+ audit_case = {
214
+ "case_id": case_id or cluster.get("cluster_id", "?"),
215
+ "cluster_id": cluster.get("cluster_id"),
216
+ "customer_ids": cluster.get("customer_ids", []),
217
+ "risk_probability": risk_probability,
218
+ "policy_decision": result.get("policy_decision"),
219
+ "llm_narrative": result.get("llm_narrative"),
220
+ "llm_confidence_statement": result.get("llm_confidence"),
221
+ "action_taken": action_taken,
222
+ "timestamp": datetime.now(timezone.utc).isoformat(),
223
+ }
224
+ _log_case(audit_case, features)
225
+ except Exception:
226
+ pass # never crash the pipeline due to audit errors
227
+
228
+ return result
229
+
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # CLI test β€” run on a single demo cluster
233
+ # ---------------------------------------------------------------------------
234
+
235
+ if __name__ == "__main__":
236
+ import json
237
+
238
+ demo_dir = os.path.join(BASE_DIR, "data", "synthetic")
239
+ scored_path = os.path.join(demo_dir, "scored_clusters.json")
240
+ orders_path = os.path.join(demo_dir, "orders.json")
241
+ graph_path = os.path.join(demo_dir, "graph.gpickle")
242
+
243
+ with open(scored_path) as f:
244
+ scored = json.load(f)
245
+
246
+ with open(orders_path) as f:
247
+ orders = json.load(f)
248
+
249
+ from scoring.features import extract_features, _load_graph
250
+ graph_obj = _load_graph(graph_path) if os.path.exists(graph_path) else None
251
+
252
+ # Use the first high-risk cluster as a test
253
+ test_cluster = scored[0]
254
+ features = extract_features(test_cluster, orders, graph=graph_obj)
255
+ risk_prob = test_cluster["risk_probability"]
256
+
257
+ print(f"Testing graph on {test_cluster['cluster_id']} "
258
+ f"(risk={risk_prob:.4f})...")
259
+
260
+ result = run_cluster(test_cluster, features, risk_prob)
261
+
262
+ print(f" Policy decision : {result['policy_decision']}")
263
+ print(f" LLM narrative : {result['llm_narrative']}")
264
+ print(f" LLM confidence : {result['llm_confidence']}")
265
+ print(f" Grounding OK : {result['grounding_ok']}")
agent/investigator.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ investigator.py
3
+ ---------------
4
+ LLM investigator node for RingGuard.
5
+
6
+ Calls Groq (llama-3.3-70b-versatile) to generate a short narrative explaining
7
+ why a cluster was flagged. Only called for auto_hold and escalate decisions
8
+ (not log_only β€” no reason to spend an LLM call on something nobody will review).
9
+
10
+ Requires GROQ_API_KEY in environment (from .env).
11
+ If GROQ_API_KEY is not set, raises RuntimeError rather than silently continuing.
12
+
13
+ Anti-hallucination boundary:
14
+ - The LLM only sees the evidence bundle (aggregated features), never raw
15
+ order-level or PII data.
16
+ - System prompt instructs the model to ONLY reference facts present in the
17
+ evidence bundle.
18
+ - Any customer ID mentioned in the narrative must be a member of the bundle's
19
+ customer_ids list (checked by run_grounding_check).
20
+ - Output validated against InvestigatorOutput Pydantic schema; retried once
21
+ with stricter reminder if parse fails.
22
+
23
+ Usage:
24
+ from agent.investigator import investigate, run_grounding_check
25
+ result = investigate(cluster, features, risk_probability, policy_decision)
26
+ is_clean = run_grounding_check(result["narrative"], cluster["customer_ids"])
27
+ """
28
+
29
+ import json
30
+ import os
31
+ import re
32
+ import sys
33
+
34
+ from dotenv import load_dotenv
35
+ from pydantic import BaseModel, ValidationError
36
+
37
+ load_dotenv()
38
+
39
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
40
+ sys.path.insert(0, BASE_DIR)
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Output schema
45
+ # ---------------------------------------------------------------------------
46
+
47
+ class InvestigatorOutput(BaseModel):
48
+ narrative: str
49
+ confidence_statement: str
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Evidence bundle builder
54
+ # ---------------------------------------------------------------------------
55
+
56
+ def build_evidence_bundle(
57
+ cluster: dict,
58
+ features: dict,
59
+ risk_probability: float,
60
+ policy_decision: str,
61
+ ) -> dict:
62
+ """
63
+ Build the evidence bundle passed to the LLM.
64
+ Contains only the aggregated facts listed in spec 10.3 β€” no raw PII.
65
+ """
66
+ return {
67
+ "customer_ids": cluster["customer_ids"],
68
+ "size": cluster["size"],
69
+ "strong_link_count": cluster["strong_link_count"],
70
+ "weak_link_count": cluster["weak_link_count"],
71
+ "formation_type": cluster.get("formation_type", "unknown"),
72
+ "return_rate": features.get("return_rate", 0.0),
73
+ "chargeback_rate": features.get("chargeback_rate", 0.0),
74
+ "shared_coupon_count": features.get("shared_coupon_count", 0),
75
+ "total_amount": features.get("total_amount", 0.0),
76
+ "risk_probability": risk_probability,
77
+ "policy_decision": policy_decision,
78
+ }
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # System prompt
83
+ # ---------------------------------------------------------------------------
84
+
85
+ SYSTEM_PROMPT = """You are a fraud analyst assistant for RingGuard, a fraud detection system.
86
+
87
+ You will receive a JSON evidence bundle describing a cluster of customers flagged as high-risk.
88
+
89
+ Your task is to write a short fraud narrative and a confidence statement.
90
+
91
+ STRICT RULES β€” violating any of these makes your response unusable:
92
+ 1. ONLY reference facts that are explicitly present in the evidence bundle.
93
+ 2. Do NOT invent customer names, order details, locations, or any fact not in the bundle.
94
+ 3. Do NOT mention any customer_id that is not in the bundle's customer_ids list.
95
+ 4. Do NOT recommend an action β€” the policy gate has already decided that.
96
+ 5. Do NOT speculate beyond what the evidence shows.
97
+ 6. Keep the narrative to 2-4 sentences. Keep the confidence statement to 1 sentence.
98
+
99
+ Respond with ONLY a valid JSON object in this exact format (no markdown, no preamble):
100
+ {"narrative": "...", "confidence_statement": "..."}"""
101
+
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # Main investigate function
105
+ # ---------------------------------------------------------------------------
106
+
107
+ def _call_groq(evidence_bundle: dict, retry_strict: bool = False) -> str:
108
+ """
109
+ Call Groq API and return the raw response text. Retries on rate limits.
110
+
111
+ Phase 3 model selection:
112
+ - groq/compound-mini was used in Phase 2 as an interim substitute.
113
+ - Phase 3 plan requires replacing it with a plain, non-agentic model.
114
+ - Available models checked at execution time: openai/gpt-oss-120b (works,
115
+ returns valid JSON directly), qwen/qwen3.6-27b (uses extended thinking,
116
+ requires higher token budget to clear <think> block before JSON).
117
+ - Selected: openai/gpt-oss-120b β€” standard chat completion, no tool calls,
118
+ deterministic JSON output, sufficient token budget at 500 tokens.
119
+ - No `tools` parameter is passed anywhere in this function (hard requirement
120
+ from Phase 3 plan Section 2.2).
121
+ """
122
+ import time
123
+ from groq import Groq, RateLimitError
124
+
125
+ api_key = os.environ.get("GROQ_API_KEY", "")
126
+ if not api_key:
127
+ raise RuntimeError(
128
+ "GROQ_API_KEY is not set in environment. "
129
+ "Add it to .env and reload before running the investigator."
130
+ )
131
+
132
+ client = Groq(api_key=api_key)
133
+
134
+ user_message = json.dumps(evidence_bundle, indent=2)
135
+ if retry_strict:
136
+ user_message += (
137
+ "\n\nIMPORTANT: Your previous response did not parse as valid JSON. "
138
+ "Respond ONLY with a JSON object: "
139
+ '{"narrative": "...", "confidence_statement": "..."} β€” no other text.'
140
+ )
141
+
142
+ # Phase 3: openai/gpt-oss-120b β€” plain chat completion, no tools parameter.
143
+ # Configurable via GROQ_MODEL env var for future model swaps.
144
+ # groq/compound-mini (Phase 2 default) is not an acceptable substitute here β€”
145
+ # it is an agentic model with tool/web-search access, violating Plan Section 2.2.
146
+ model_name = os.environ.get("GROQ_MODEL", "openai/gpt-oss-120b")
147
+
148
+ for attempt in range(3):
149
+ try:
150
+ response = client.chat.completions.create(
151
+ model=model_name,
152
+ messages=[
153
+ {"role": "system", "content": SYSTEM_PROMPT},
154
+ {"role": "user", "content": user_message},
155
+ ],
156
+ temperature=0.1, # low temperature for deterministic output
157
+ max_tokens=500, # no `tools` parameter β€” plain chat completion
158
+ )
159
+ return response.choices[0].message.content.strip()
160
+ except RateLimitError as e:
161
+ if attempt < 2:
162
+ wait = 30 * (attempt + 1)
163
+ print(f"\n [rate_limit] waiting {wait}s before retry {attempt+2}/3...")
164
+ time.sleep(wait)
165
+ else:
166
+ raise
167
+
168
+
169
+ def _parse_response(raw: str) -> InvestigatorOutput:
170
+ """Parse and validate the LLM response against InvestigatorOutput schema."""
171
+ # Strip any accidental markdown fencing
172
+ raw = raw.strip()
173
+ if raw.startswith("```"):
174
+ raw = re.sub(r"^```[a-z]*\n?", "", raw)
175
+ raw = re.sub(r"\n?```$", "", raw)
176
+ raw = raw.strip()
177
+
178
+ data = json.loads(raw)
179
+ return InvestigatorOutput(**data)
180
+
181
+
182
+ def investigate(
183
+ cluster: dict,
184
+ features: dict,
185
+ risk_probability: float,
186
+ policy_decision: str,
187
+ ) -> dict:
188
+ """
189
+ Call the LLM investigator for a flagged cluster.
190
+
191
+ Parameters
192
+ ----------
193
+ cluster : dict
194
+ Candidate cluster record (from candidate_clusters.json or scored_clusters.json).
195
+ features : dict
196
+ Feature dict for this cluster (from scoring/features.py).
197
+ risk_probability : float
198
+ Risk probability from the model.
199
+ policy_decision : str
200
+ Policy decision (must be "auto_hold" or "escalate" β€” callers should not
201
+ call this for "log_only" decisions).
202
+
203
+ Returns
204
+ -------
205
+ dict with keys: narrative, confidence_statement, grounding_ok, raw_response
206
+ """
207
+ evidence = build_evidence_bundle(cluster, features, risk_probability, policy_decision)
208
+
209
+ # First attempt
210
+ raw = _call_groq(evidence)
211
+ parse_error = None
212
+ output = None
213
+
214
+ try:
215
+ output = _parse_response(raw)
216
+ except (json.JSONDecodeError, ValidationError, Exception) as e:
217
+ parse_error = str(e)
218
+
219
+ if output is None:
220
+ # Retry once with stricter reminder (spec 10.5)
221
+ raw2 = _call_groq(evidence, retry_strict=True)
222
+ try:
223
+ output = _parse_response(raw2)
224
+ raw = raw2 # use the successful response
225
+ except (json.JSONDecodeError, ValidationError, Exception) as e2:
226
+ # Both attempts failed β€” log and raise
227
+ raise RuntimeError(
228
+ f"LLM response failed to parse after 2 attempts. "
229
+ f"First error: {parse_error}. Second error: {e2}. "
230
+ f"Raw response (last): {raw2}"
231
+ )
232
+
233
+ # 10.6 β€” grounding check: no customer ID in narrative that isn't in the bundle
234
+ grounding_ok = run_grounding_check(output.narrative, evidence["customer_ids"])
235
+
236
+ return {
237
+ "narrative": output.narrative,
238
+ "confidence_statement": output.confidence_statement,
239
+ "grounding_ok": grounding_ok,
240
+ "raw_response": raw,
241
+ "evidence_bundle": evidence,
242
+ }
243
+
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # 10.6 β€” Grounding check
247
+ # ---------------------------------------------------------------------------
248
+
249
+ def run_grounding_check(narrative: str, customer_ids: list[str]) -> bool:
250
+ """
251
+ Check that any customer ID (CUSTXXXX pattern) mentioned in the narrative
252
+ is a member of customer_ids.
253
+
254
+ Returns True if the narrative is grounded (no hallucinated IDs).
255
+ Returns False if the narrative references a customer ID not in the bundle.
256
+ """
257
+ # Find all CUST-prefixed IDs mentioned in the narrative
258
+ mentioned_ids = set(re.findall(r"CUST\d+", narrative))
259
+ allowed_ids = set(customer_ids)
260
+
261
+ hallucinated = mentioned_ids - allowed_ids
262
+ if hallucinated:
263
+ return False
264
+ return True
agent/policy.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ policy.py
3
+ ---------
4
+ Deterministic policy gate for RingGuard.
5
+
6
+ Pure function, no LLM calls, no side effects.
7
+ This is the ONLY place in the system that decides whether a money-adjacent
8
+ action is triggered β€” it stays boring and deterministic on purpose.
9
+
10
+ Phase 3 threshold calibration (v3.0):
11
+ The original Phase 2 auto_hold threshold of ≀₹5,000 was found to be
12
+ incompatible with the trained model's scoring range during Phase 3 Section 1.
13
+ The Phase 2 model's minimum positive avg_order_amount is β‚Ή3,249; a 3-member
14
+ ring with 6 orders at that level produces β‰ˆβ‚Ή19,800 total β€” above the old cap.
15
+ Updated to ≀₹25,000 to allow small-cell rings with realistic amounts through.
16
+ Documented in phase3_summary.md under Key Decisions.
17
+
18
+ Thresholds (Phase 3, v3.0):
19
+ risk >= 0.85 and exposure <= 25000 β†’ auto_hold
20
+ risk >= 0.85 and exposure > 25000 β†’ escalate
21
+ 0.50 <= risk < 0.85 β†’ escalate
22
+ risk < 0.50 β†’ log_only
23
+
24
+ Usage:
25
+ from agent.policy import decide
26
+ action = decide(risk_probability=0.91, exposure_rupees=19800.0)
27
+ # β†’ "auto_hold"
28
+ """
29
+
30
+
31
+ def decide(risk_probability: float, exposure_rupees: float) -> str:
32
+ """
33
+ Parameters
34
+ ----------
35
+ risk_probability : float
36
+ Model output probability in [0, 1].
37
+ exposure_rupees : float
38
+ Total order amount across the cluster (from features.total_amount).
39
+
40
+ Returns
41
+ -------
42
+ str β€” one of: "auto_hold", "escalate", "log_only"
43
+ """
44
+ if risk_probability >= 0.85 and exposure_rupees <= 25000:
45
+ return "auto_hold"
46
+ if risk_probability >= 0.85 and exposure_rupees > 25000:
47
+ return "escalate"
48
+ if 0.5 <= risk_probability < 0.85:
49
+ return "escalate"
50
+ return "log_only"
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Inline assertions β€” the money-decision boundary warrants a few spot checks
55
+ # ---------------------------------------------------------------------------
56
+ if __name__ == "__main__":
57
+ assert decide(0.91, 3000.0) == "auto_hold", "high risk, low exposure"
58
+ assert decide(0.91, 30000.0) == "escalate", "high risk, high exposure"
59
+ assert decide(0.85, 25000.0) == "auto_hold", "boundary: exactly 0.85, exactly 25000"
60
+ assert decide(0.85, 25001.0) == "escalate", "boundary: exactly 0.85, just over 25000"
61
+ assert decide(0.70, 0.0) == "escalate", "medium risk"
62
+ assert decide(0.50, 9999.0) == "escalate", "lower boundary medium risk"
63
+ assert decide(0.49, 9999.0) == "log_only", "below medium threshold"
64
+ assert decide(0.0, 0.0) == "log_only", "zero risk"
65
+ print("All policy assertions passed.")
agent/run_demo.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ run_demo.py
3
+ -----------
4
+ End-to-end demo run: score every cluster in data/synthetic/scored_clusters.json
5
+ through the full LangGraph pipeline and save results to
6
+ data/synthetic/cases_demo.json.
7
+
8
+ Usage:
9
+ python agent/run_demo.py
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+ import time
16
+ from datetime import datetime, timezone
17
+
18
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19
+ sys.path.insert(0, BASE_DIR)
20
+
21
+ from dotenv import load_dotenv
22
+ load_dotenv()
23
+
24
+ from scoring.features import extract_features, _load_json, _load_graph, FEATURE_COLUMNS
25
+ from agent.graph import run_cluster
26
+ from agent.investigator import run_grounding_check
27
+
28
+
29
+ DEMO_DIR = os.path.join(BASE_DIR, "data", "synthetic")
30
+ OUT_PATH = os.path.join(DEMO_DIR, "cases_demo.json")
31
+
32
+
33
+ def run_demo():
34
+ scored_path = os.path.join(DEMO_DIR, "scored_clusters.json")
35
+ orders_path = os.path.join(DEMO_DIR, "orders.json")
36
+ graph_path = os.path.join(DEMO_DIR, "graph.gpickle")
37
+
38
+ print(f"Loading demo data from {DEMO_DIR}...")
39
+ scored = _load_json(scored_path)
40
+ orders = _load_json(orders_path)
41
+ graph = _load_graph(graph_path) if os.path.exists(graph_path) else None
42
+
43
+ print(f" {len(scored)} clusters to process\n")
44
+
45
+ cases = []
46
+ grounding_failures = []
47
+
48
+ for idx, cluster in enumerate(scored, start=1):
49
+ cid = cluster["cluster_id"]
50
+ risk = cluster["risk_probability"]
51
+ print(f"[{idx:02d}/{len(scored)}] {cid} risk={risk:.4f} formation={cluster.get('formation_type','?')}", end=" ", flush=True)
52
+
53
+ features = extract_features(cluster, orders, graph=graph)
54
+ result = run_cluster(cluster, features, risk)
55
+
56
+ decision = result["policy_decision"]
57
+ narrative = result["llm_narrative"]
58
+ confidence = result["llm_confidence"]
59
+ grounding = result.get("grounding_ok")
60
+
61
+ print(f"-> {decision}")
62
+
63
+ # Small sleep between LLM calls to stay within free-tier TPM limits
64
+ # (only needed for escalate/auto_hold cases that call the LLM)
65
+ if decision in ("auto_hold", "escalate"):
66
+ time.sleep(3)
67
+
68
+ # 10.6 β€” grounding check for every narrative that was generated
69
+ if narrative and not narrative.startswith("[LLM_ERROR]"):
70
+ gok = run_grounding_check(narrative, cluster["customer_ids"])
71
+ if not gok:
72
+ grounding_failures.append({
73
+ "cluster_id": cid,
74
+ "narrative": narrative,
75
+ "customer_ids": cluster["customer_ids"],
76
+ })
77
+ print(f" [GROUNDING FAIL] narrative references customer ID not in bundle!")
78
+
79
+ case = {
80
+ "case_id": f"CASE{idx:02d}",
81
+ "cluster_id": cid,
82
+ "customer_ids": cluster["customer_ids"],
83
+ "risk_probability": risk,
84
+ "policy_decision": decision,
85
+ "llm_narrative": narrative,
86
+ "llm_confidence_statement": confidence,
87
+ "action_taken": None, # Phase 3 populates via Razorpay MCP
88
+ "timestamp": datetime.now(timezone.utc).isoformat(),
89
+ }
90
+ cases.append(case)
91
+
92
+ # Write cases_demo.json
93
+ with open(OUT_PATH, "w", encoding="utf-8") as f:
94
+ json.dump(cases, f, indent=2, ensure_ascii=False)
95
+ print(f"\nSaved {len(cases)} cases to {OUT_PATH}")
96
+
97
+ # Summary
98
+ from collections import Counter
99
+ decision_counts = Counter(c["policy_decision"] for c in cases)
100
+ llm_called = sum(1 for c in cases if c["llm_narrative"] and not c["llm_narrative"].startswith("[LLM_ERROR]"))
101
+ llm_errors = sum(1 for c in cases if c["llm_narrative"] and c["llm_narrative"].startswith("[LLM_ERROR]"))
102
+
103
+ print("\n========== Demo Run Summary ==========")
104
+ print(f" Total cases : {len(cases)}")
105
+ for action, count in sorted(decision_counts.items()):
106
+ print(f" {action:15s} : {count}")
107
+ print(f" LLM calls made : {llm_called}")
108
+ print(f" LLM errors : {llm_errors}")
109
+ print(f" Grounding failures : {len(grounding_failures)}")
110
+ print("=======================================")
111
+
112
+ if grounding_failures:
113
+ print("\n[WARNING] Grounding failures detected:")
114
+ for gf in grounding_failures:
115
+ print(f" {gf['cluster_id']}: {gf['narrative']}")
116
+
117
+ return cases, grounding_failures, decision_counts
118
+
119
+
120
+ if __name__ == "__main__":
121
+ run_demo()
api/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
api/main.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ main.py
3
+ -------
4
+ Phase 3 Section 5 β€” FastAPI dashboard for RingGuard.
5
+
6
+ Endpoints:
7
+ GET / β€” HTML dashboard: table of all cases
8
+ GET /cases β€” JSON list of all cases (from cases_demo.json)
9
+ GET /cases/{case_id} β€” JSON detail for one case + full audit chain
10
+ GET /health β€” health check
11
+
12
+ The dashboard reads directly from cases_demo.json and audit_log.jsonl.
13
+ No database, no auth β€” demo only.
14
+
15
+ Usage:
16
+ uvicorn api.main:app --reload --port 7860
17
+ """
18
+
19
+ import json
20
+ import os
21
+ from typing import Optional
22
+
23
+ from fastapi import FastAPI, HTTPException
24
+ from fastapi.responses import HTMLResponse, JSONResponse
25
+
26
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27
+ CASES_PATH = os.path.join(BASE_DIR, "data", "synthetic", "cases_demo.json")
28
+ AUDIT_PATH = os.path.join(BASE_DIR, "audit", "audit_log.jsonl")
29
+
30
+ app = FastAPI(
31
+ title="RingGuard Dashboard",
32
+ description="Phase 3 β€” Fraud ring detection results and audit trail",
33
+ version="3.0.0",
34
+ )
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Data loaders
39
+ # ---------------------------------------------------------------------------
40
+
41
+ def _load_cases() -> list:
42
+ if not os.path.exists(CASES_PATH):
43
+ return []
44
+ with open(CASES_PATH, encoding="utf-8") as f:
45
+ return json.load(f)
46
+
47
+
48
+ def _load_audit() -> list:
49
+ if not os.path.exists(AUDIT_PATH):
50
+ return []
51
+ records = []
52
+ with open(AUDIT_PATH, encoding="utf-8") as f:
53
+ for line in f:
54
+ line = line.strip()
55
+ if line:
56
+ records.append(json.loads(line))
57
+ return records
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # API endpoints
62
+ # ---------------------------------------------------------------------------
63
+
64
+ @app.get("/health")
65
+ def health():
66
+ return {"status": "ok", "version": "3.0.0"}
67
+
68
+
69
+ @app.get("/cases", response_class=JSONResponse)
70
+ def get_cases():
71
+ """Return all cases as a JSON list."""
72
+ cases = _load_cases()
73
+ # Return a summary view (without full narrative for list)
74
+ summary = []
75
+ for c in cases:
76
+ summary.append({
77
+ "case_id": c.get("case_id"),
78
+ "cluster_id": c.get("cluster_id"),
79
+ "cluster_size": len(c.get("customer_ids", [])),
80
+ "risk_score": round(c.get("risk_probability", 0), 4),
81
+ "policy_decision": c.get("policy_decision"),
82
+ "action_taken": bool(c.get("action_taken") and c["action_taken"].get("order_id")),
83
+ "timestamp": c.get("timestamp"),
84
+ })
85
+ return summary
86
+
87
+
88
+ @app.get("/cases/{case_id}", response_class=JSONResponse)
89
+ def get_case_detail(case_id: str):
90
+ """Return full detail for one case, including audit chain."""
91
+ cases = _load_cases()
92
+ case = next((c for c in cases if c.get("case_id") == case_id), None)
93
+ if not case:
94
+ raise HTTPException(status_code=404, detail=f"Case {case_id} not found")
95
+
96
+ # Find audit records for this case
97
+ audit_records = [r for r in _load_audit() if r.get("case_id") == case_id]
98
+
99
+ return {
100
+ "case": case,
101
+ "audit_chain": audit_records,
102
+ }
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # HTML dashboard
107
+ # ---------------------------------------------------------------------------
108
+
109
+ def _decision_badge(decision: str) -> str:
110
+ colours = {
111
+ "auto_hold": "#c0392b",
112
+ "escalate": "#e67e22",
113
+ "log_only": "#27ae60",
114
+ }
115
+ colour = colours.get(decision, "#7f8c8d")
116
+ return (
117
+ f'<span style="background:{colour};color:#fff;padding:2px 8px;'
118
+ f'border-radius:4px;font-size:11px;font-weight:600;">'
119
+ f'{decision.upper()}</span>'
120
+ )
121
+
122
+
123
+ def _build_dashboard(cases: list) -> str:
124
+ rows = ""
125
+ for c in cases:
126
+ decision = c.get("policy_decision", "")
127
+ badge = _decision_badge(decision)
128
+ risk = round(c.get("risk_probability", 0) * 100, 1)
129
+ cluster = c.get("cluster_id", "")
130
+ case_id = c.get("case_id", "")
131
+ size = len(c.get("customer_ids", []))
132
+ action = c.get("action_taken") or {}
133
+ order_id = action.get("order_id") or "β€”"
134
+ verified = "Yes" if action.get("notes_verified") else "β€”"
135
+
136
+ rows += (
137
+ f'<tr onclick="window.location=\'/cases/{case_id}\'" style="cursor:pointer">'
138
+ f'<td>{case_id}</td>'
139
+ f'<td>{cluster}</td>'
140
+ f'<td style="text-align:center">{size}</td>'
141
+ f'<td style="text-align:right">{risk}%</td>'
142
+ f'<td>{badge}</td>'
143
+ f'<td style="font-size:11px;color:#57606a">{order_id}</td>'
144
+ f'<td style="text-align:center">{verified}</td>'
145
+ f'</tr>\n'
146
+ )
147
+
148
+ auto_holds = sum(1 for c in cases if c.get("policy_decision") == "auto_hold")
149
+ escalates = sum(1 for c in cases if c.get("policy_decision") == "escalate")
150
+ log_only = sum(1 for c in cases if c.get("policy_decision") == "log_only")
151
+
152
+ return f"""<!DOCTYPE html>
153
+ <html lang="en">
154
+ <head>
155
+ <meta charset="utf-8">
156
+ <meta name="viewport" content="width=device-width, initial-scale=1">
157
+ <title>RingGuard β€” Fraud Detection Dashboard</title>
158
+ <style>
159
+ * {{ box-sizing: border-box; margin: 0; padding: 0; }}
160
+ body {{ font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
161
+ font-size: 14px; line-height: 1.6; background: #f7f8fa; color: #1f2328; }}
162
+ .container {{ max-width: 960px; margin: 0 auto; padding: 24px 16px; }}
163
+ h1 {{ font-size: 22px; font-weight: 700; margin-bottom: 4px; }}
164
+ .subtitle {{ color: #57606a; font-size: 13px; margin-bottom: 24px; }}
165
+ .stats {{ display: flex; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }}
166
+ .stat-card {{ background: #fff; border: 1px solid #e5e7eb; border-radius: 6px;
167
+ padding: 12px 20px; flex: 1; min-width: 140px; }}
168
+ .stat-card .label {{ font-size: 11px; color: #57606a; text-transform: uppercase;
169
+ letter-spacing: 0.05em; }}
170
+ .stat-card .value {{ font-size: 24px; font-weight: 700; }}
171
+ .stat-card.hold .value {{ color: #c0392b; }}
172
+ .stat-card.escalate .value {{ color: #e67e22; }}
173
+ .stat-card.log .value {{ color: #27ae60; }}
174
+ table {{ width: 100%; border-collapse: collapse; background: #fff;
175
+ border: 1px solid #e5e7eb; border-radius: 6px; overflow: hidden; }}
176
+ th {{ background: #f7f8fa; border-bottom: 2px solid #e5e7eb; padding: 8px 12px;
177
+ text-align: left; font-size: 12px; font-weight: 600; color: #57606a;
178
+ text-transform: uppercase; letter-spacing: 0.04em; }}
179
+ td {{ padding: 8px 12px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; }}
180
+ tr:last-child td {{ border-bottom: none; }}
181
+ tr:hover td {{ background: #f7f8fa; }}
182
+ .footer {{ text-align: center; font-size: 12px; color: #57606a; margin-top: 32px;
183
+ padding-top: 16px; border-top: 1px solid #e5e7eb; }}
184
+ a {{ color: #3b82d4; text-decoration: none; }}
185
+ a:hover {{ text-decoration: underline; }}
186
+ </style>
187
+ </head>
188
+ <body>
189
+ <div class="container">
190
+ <h1>RingGuard β€” Fraud Detection Dashboard</h1>
191
+ <p class="subtitle">Phase 3 Β· {len(cases)} cases Β· LightGBM + LangGraph + Razorpay test-mode</p>
192
+
193
+ <div class="stats">
194
+ <div class="stat-card hold">
195
+ <div class="label">Auto Hold</div>
196
+ <div class="value">{auto_holds}</div>
197
+ </div>
198
+ <div class="stat-card escalate">
199
+ <div class="label">Escalate</div>
200
+ <div class="value">{escalates}</div>
201
+ </div>
202
+ <div class="stat-card log">
203
+ <div class="label">Log Only</div>
204
+ <div class="value">{log_only}</div>
205
+ </div>
206
+ <div class="stat-card">
207
+ <div class="label">Total Cases</div>
208
+ <div class="value">{len(cases)}</div>
209
+ </div>
210
+ </div>
211
+
212
+ <table>
213
+ <thead>
214
+ <tr>
215
+ <th>Case</th>
216
+ <th>Cluster</th>
217
+ <th style="text-align:center">Size</th>
218
+ <th style="text-align:right">Risk Score</th>
219
+ <th>Decision</th>
220
+ <th>Razorpay Order</th>
221
+ <th style="text-align:center">Verified</th>
222
+ </tr>
223
+ </thead>
224
+ <tbody>
225
+ {rows} </tbody>
226
+ </table>
227
+
228
+ <p style="margin-top:12px;font-size:12px;color:#57606a">
229
+ Click any row to view the full evidence bundle, LLM narrative, and audit chain.
230
+ </p>
231
+
232
+ <div class="footer">Made with IBM Bob &middot; RingGuard v3.0</div>
233
+ </div>
234
+ </body>
235
+ </html>"""
236
+
237
+
238
+ @app.get("/", response_class=HTMLResponse)
239
+ def dashboard():
240
+ """Serve the HTML dashboard."""
241
+ cases = _load_cases()
242
+ html = _build_dashboard(cases)
243
+ return HTMLResponse(content=html)
244
+
245
+
246
+ @app.get("/cases/{case_id}/detail", response_class=HTMLResponse)
247
+ def case_detail_html(case_id: str):
248
+ """Serve HTML detail page for a single case."""
249
+ cases = _load_cases()
250
+ case = next((c for c in cases if c.get("case_id") == case_id), None)
251
+ if not case:
252
+ raise HTTPException(status_code=404, detail=f"Case {case_id} not found")
253
+
254
+ audit_records = [r for r in _load_audit() if r.get("case_id") == case_id]
255
+
256
+ decision = case.get("policy_decision", "")
257
+ badge = _decision_badge(decision)
258
+ risk = round(case.get("risk_probability", 0) * 100, 2)
259
+ narrative = case.get("llm_narrative") or "<em>No narrative (log_only case)</em>"
260
+ confidence = case.get("llm_confidence_statement") or "β€”"
261
+ members = ", ".join(case.get("customer_ids", []))
262
+
263
+ action = case.get("action_taken") or {}
264
+ order_id = action.get("order_id") or "β€”"
265
+ verified = "Yes" if action.get("notes_verified") else "β€”"
266
+ tool_used = action.get("tool") or "β€”"
267
+ timestamp = action.get("timestamp") or "β€”"
268
+
269
+ audit_rows = ""
270
+ for rec in audit_records:
271
+ ts = rec.get("timestamp", "")[:19].replace("T", " ")
272
+ audit_rows += (
273
+ f'<tr>'
274
+ f'<td>{ts}</td>'
275
+ f'<td>{rec.get("policy_decision","")}</td>'
276
+ f'<td style="font-size:11px">{rec.get("reason_if_not_auto_actioned") or "β€”"}</td>'
277
+ f'</tr>\n'
278
+ )
279
+
280
+ html = f"""<!DOCTYPE html>
281
+ <html lang="en">
282
+ <head>
283
+ <meta charset="utf-8">
284
+ <title>RingGuard β€” {case_id}</title>
285
+ <style>
286
+ * {{ box-sizing: border-box; margin: 0; padding: 0; }}
287
+ body {{ font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
288
+ font-size: 14px; line-height: 1.6; background: #f7f8fa; color: #1f2328; }}
289
+ .container {{ max-width: 760px; margin: 0 auto; padding: 24px 16px; }}
290
+ h1 {{ font-size: 20px; font-weight: 700; margin-bottom: 4px; }}
291
+ .back {{ font-size: 13px; color: #3b82d4; margin-bottom: 16px; display: block; }}
292
+ .card {{ background: #fff; border: 1px solid #e5e7eb; border-radius: 6px;
293
+ padding: 16px; margin-bottom: 16px; }}
294
+ .card h2 {{ font-size: 13px; font-weight: 600; color: #57606a; text-transform: uppercase;
295
+ letter-spacing: 0.05em; margin-bottom: 8px; }}
296
+ .row {{ display: flex; gap: 24px; margin-bottom: 6px; flex-wrap: wrap; }}
297
+ .kv {{ flex: 1; min-width: 200px; }}
298
+ .kv .k {{ font-size: 11px; color: #57606a; }}
299
+ .kv .v {{ font-weight: 500; }}
300
+ .narrative {{ background: #f7f8fa; border-left: 3px solid #3b82d4;
301
+ padding: 10px 14px; border-radius: 0 4px 4px 0; font-style: italic; }}
302
+ table {{ width: 100%; border-collapse: collapse; }}
303
+ th {{ text-align: left; font-size: 12px; color: #57606a; padding: 4px 8px;
304
+ border-bottom: 1px solid #e5e7eb; }}
305
+ td {{ padding: 6px 8px; border-bottom: 1px solid #f0f0f0; }}
306
+ .footer {{ text-align: center; font-size: 12px; color: #57606a; margin-top: 32px;
307
+ padding-top: 16px; border-top: 1px solid #e5e7eb; }}
308
+ span.badge {{ display: inline-block; }}
309
+ </style>
310
+ </head>
311
+ <body>
312
+ <div class="container">
313
+ <a class="back" href="/">&#8592; Back to all cases</a>
314
+ <h1>{case_id} β€” {case.get("cluster_id","")}</h1>
315
+
316
+ <div class="card">
317
+ <h2>Summary</h2>
318
+ <div class="row">
319
+ <div class="kv"><div class="k">Risk Score</div><div class="v">{risk}%</div></div>
320
+ <div class="kv"><div class="k">Decision</div><div class="v">{badge}</div></div>
321
+ <div class="kv"><div class="k">Cluster Size</div><div class="v">{len(case.get("customer_ids",[]))}</div></div>
322
+ </div>
323
+ <div class="row">
324
+ <div class="kv"><div class="k">Cluster Members</div><div class="v" style="font-size:12px">{members}</div></div>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="card">
329
+ <h2>LLM Narrative (openai/gpt-oss-120b via Groq)</h2>
330
+ <div class="narrative">{narrative}</div>
331
+ <div style="margin-top:8px;font-size:12px;color:#57606a">
332
+ <strong>Confidence:</strong> {confidence}
333
+ </div>
334
+ </div>
335
+
336
+ <div class="card">
337
+ <h2>Razorpay Action (Test Mode)</h2>
338
+ <div class="row">
339
+ <div class="kv"><div class="k">Order ID</div><div class="v" style="font-size:12px">{order_id}</div></div>
340
+ <div class="kv"><div class="k">Notes Verified</div><div class="v">{verified}</div></div>
341
+ </div>
342
+ <div class="row">
343
+ <div class="kv"><div class="k">Tool Used</div><div class="v" style="font-size:11px">{tool_used}</div></div>
344
+ <div class="kv"><div class="k">Timestamp</div><div class="v" style="font-size:12px">{timestamp[:19] if len(timestamp)>=19 else timestamp}</div></div>
345
+ </div>
346
+ </div>
347
+
348
+ <div class="card">
349
+ <h2>Audit Chain</h2>
350
+ {f'<table><thead><tr><th>Timestamp</th><th>Decision</th><th>Reason</th></tr></thead><tbody>{audit_rows}</tbody></table>' if audit_rows else '<p style="color:#57606a;font-size:13px">No audit records found for this case.</p>'}
351
+ </div>
352
+
353
+ <div class="footer">Made with IBM Bob &middot; RingGuard v3.0</div>
354
+ </div>
355
+ </body>
356
+ </html>"""
357
+ return HTMLResponse(content=html)
audit/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
audit/audit_log.jsonl ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"case_id": "CASE01", "timestamp": "2026-08-22T02:48:44.328671+00:00", "cluster_members": ["CUST0041", "CUST0042", "CUST0043", "CUST0044"], "evidence_bundle": {"cluster_id": "CAND01", "size": 4, "customer_ids": ["CUST0041", "CUST0042", "CUST0043", "CUST0044"], "formation_type": null}, "graph_score_inputs": {"size": 4, "strong_link_count": 6, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 12, "total_amount": 58186, "avg_order_amount": 4848.833333333333, "return_rate": 0.3333333333333333, "chargeback_rate": 0.16666666666666666, "shared_coupon_count": 0, "time_span_days": 118.78427083333334, "order_velocity": 0.10102347655808104, "formation_type_strong": 1}, "lightgbm_probability": 0.9958773824170732, "llm_narrative": "The four customers (CUST0041, CUST0042, CUST0043, CUST0044) form a strong‑anchored cluster with six strong links and no weak links, indicating tightly coordinated activity. Their combined purchase total of $58,186 is accompanied by a 33.3% return rate and a 16.7% chargeback rate, further supporting fraudulent behavior.", "llm_confidence_statement": "The model assigns a risk probability of 99.6%, indicating very high confidence in the escalation decision.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeidafHmbAnDR", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:10.941027+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE01", "cluster_id": "CAND01", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9959", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:10.102605+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9959 >= 0.85 but total_amount=β‚Ή58186 > β‚Ή25,000 auto_hold cap"}
2
+ {"case_id": "CASE02", "timestamp": "2026-08-22T02:48:44.330726+00:00", "cluster_members": ["CUST0009", "CUST0010", "CUST0011", "CUST0012", "CUST0013"], "evidence_bundle": {"cluster_id": "CAND02", "size": 5, "customer_ids": ["CUST0009", "CUST0010", "CUST0011", "CUST0012", "CUST0013"], "formation_type": null}, "graph_score_inputs": {"size": 5, "strong_link_count": 10, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 17, "total_amount": 59830, "avg_order_amount": 3519.4117647058824, "return_rate": 0.47058823529411764, "chargeback_rate": 0.17647058823529413, "shared_coupon_count": 0, "time_span_days": 153.46033564814815, "order_velocity": 0.11077781061926893, "formation_type_strong": 1}, "lightgbm_probability": 0.9968053865328341, "llm_narrative": "The five customers (CUST0009‑CUST0013) form a strong‑anchored cluster with ten strong links and no weak links. The group shows a return rate of 47% and a chargeback rate of 18%, with total transaction volume of $59,830. The risk model assigns a probability of 99.7%, resulting in an escalation decision.", "llm_confidence_statement": "We are highly confident in this assessment given the very high risk probability and supporting metrics.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeieevTUEtDNP", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:11.992374+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE02", "cluster_id": "CAND02", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9968", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:11.088678+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9968 >= 0.85 but total_amount=β‚Ή59830 > β‚Ή25,000 auto_hold cap"}
3
+ {"case_id": "CASE03", "timestamp": "2026-08-22T02:48:44.331690+00:00", "cluster_members": ["CUST0001", "CUST0002", "CUST0003"], "evidence_bundle": {"cluster_id": "CAND03", "size": 3, "customer_ids": ["CUST0001", "CUST0002", "CUST0003"], "formation_type": null}, "graph_score_inputs": {"size": 3, "strong_link_count": 3, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 8, "total_amount": 32897, "avg_order_amount": 4112.125, "return_rate": 0.25, "chargeback_rate": 0.125, "shared_coupon_count": 1, "time_span_days": 127.71112268518519, "order_velocity": 0.06264137243331916, "formation_type_strong": 1}, "lightgbm_probability": 0.9950028638127228, "llm_narrative": "The three customers (CUST0001, CUST0002, CUST0003) are connected by three strong links, forming a strong‑anchored cluster. This cluster accounts for a total transaction amount of 32,897, includes one shared coupon, and exhibits a 25% return rate and a 12.5% chargeback rate, resulting in a risk probability of 99.5%.", "llm_confidence_statement": "The model’s confidence in this risk assessment is extremely high.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeifof2nHqiyN", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:13.232792+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE03", "cluster_id": "CAND03", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.995", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:12.186378+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9950 >= 0.85 but total_amount=β‚Ή32897 > β‚Ή25,000 auto_hold cap"}
4
+ {"case_id": "CASE04", "timestamp": "2026-08-22T02:48:44.333027+00:00", "cluster_members": ["CUST0014", "CUST0015", "CUST0016", "CUST0017", "CUST0018"], "evidence_bundle": {"cluster_id": "CAND04", "size": 5, "customer_ids": ["CUST0014", "CUST0015", "CUST0016", "CUST0017", "CUST0018"], "formation_type": null}, "graph_score_inputs": {"size": 5, "strong_link_count": 10, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 16, "total_amount": 62087, "avg_order_amount": 3880.4375, "return_rate": 0.5, "chargeback_rate": 0.125, "shared_coupon_count": 1, "time_span_days": 158.5827314814815, "order_velocity": 0.10089370923635782, "formation_type_strong": 1}, "lightgbm_probability": 0.9955003588432927, "llm_narrative": "The five customers (CUST0014‑CUST0018) form a strong‑anchored cluster with ten strong links and no weak links, collectively spending $62,087. The group exhibits a 50% return rate, a 12.5% chargeback rate, and shares a single coupon, indicating coordinated risky behavior.", "llm_confidence_statement": "Given the 0.9955 risk probability, we are highly confident that this cluster represents a high‑risk fraud pattern.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeihAqIgYfEsF", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:14.280655+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE04", "cluster_id": "CAND04", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9955", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:13.364816+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9955 >= 0.85 but total_amount=β‚Ή62087 > β‚Ή25,000 auto_hold cap"}
5
+ {"case_id": "CASE05", "timestamp": "2026-08-22T02:48:44.333027+00:00", "cluster_members": ["CUST0033", "CUST0034", "CUST0035"], "evidence_bundle": {"cluster_id": "CAND05", "size": 3, "customer_ids": ["CUST0033", "CUST0034", "CUST0035"], "formation_type": null}, "graph_score_inputs": {"size": 3, "strong_link_count": 3, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 11, "total_amount": 50134, "avg_order_amount": 4557.636363636364, "return_rate": 0.18181818181818182, "chargeback_rate": 0.09090909090909091, "shared_coupon_count": 1, "time_span_days": 170.66708333333332, "order_velocity": 0.06445296764412196, "formation_type_strong": 1}, "lightgbm_probability": 0.9969893801631614, "llm_narrative": "The three customers (CUST0033, CUST0034, CUST0035) are connected by three strong links, forming a strong‑anchored cluster with a shared coupon and a combined transaction volume of $50,134. The cluster exhibits a return rate of 18.2% and a chargeback rate of 9.1%, and its calculated risk probability is 99.7%.", "llm_confidence_statement": "We are highly confident in the fraud assessment given the strong linkage, high risk probability, and notable financial metrics.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeiiK16ZPVegj", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:15.299833+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE05", "cluster_id": "CAND05", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.997", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:14.523378+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9970 >= 0.85 but total_amount=β‚Ή50134 > β‚Ή25,000 auto_hold cap"}
6
+ {"case_id": "CASE06", "timestamp": "2026-08-22T02:48:44.333027+00:00", "cluster_members": ["CUST0036", "CUST0037", "CUST0038", "CUST0039", "CUST0040"], "evidence_bundle": {"cluster_id": "CAND06", "size": 5, "customer_ids": ["CUST0036", "CUST0037", "CUST0038", "CUST0039", "CUST0040"], "formation_type": null}, "graph_score_inputs": {"size": 5, "strong_link_count": 10, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 15, "total_amount": 62270, "avg_order_amount": 4151.333333333333, "return_rate": 0.26666666666666666, "chargeback_rate": 0.2, "shared_coupon_count": 0, "time_span_days": 145.8427199074074, "order_velocity": 0.10285052287507526, "formation_type_strong": 1}, "lightgbm_probability": 0.9969377389203431, "llm_narrative": "The five customers (CUST0036‑CUST0040) form a strong‑anchored cluster with ten strong links and no weak links, collectively transacting $62,270. Their return rate of 26.7% and chargeback rate of 20% are elevated, and the absence of shared coupons suggests the links are not promotional. These characteristics indicate coordinated fraudulent behavior.", "llm_confidence_statement": "We are highly confident in this assessment, reflected by a risk probability of 0.997.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeijQj96lsR88", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:16.209387+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE06", "cluster_id": "CAND06", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9969", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:15.427579+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9969 >= 0.85 but total_amount=β‚Ή62270 > β‚Ή25,000 auto_hold cap"}
7
+ {"case_id": "CASE07", "timestamp": "2026-08-22T02:48:44.333027+00:00", "cluster_members": ["CUST0019", "CUST0020", "CUST0021", "CUST0022", "CUST0023"], "evidence_bundle": {"cluster_id": "CAND07", "size": 5, "customer_ids": ["CUST0019", "CUST0020", "CUST0021", "CUST0022", "CUST0023"], "formation_type": null}, "graph_score_inputs": {"size": 5, "strong_link_count": 10, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 18, "total_amount": 70841, "avg_order_amount": 3935.6111111111113, "return_rate": 0.3333333333333333, "chargeback_rate": 0.05555555555555555, "shared_coupon_count": 0, "time_span_days": 144.96715277777778, "order_velocity": 0.12416605869049836, "formation_type_strong": 1}, "lightgbm_probability": 0.9940850384979173, "llm_narrative": "A cluster of five customers (CUST0019‑CUST0023) is connected by ten strong links with no weak links, forming a strong‑anchored network. The group has generated a total transaction amount of 70,841, a return rate of 33.33% and a chargeback rate of 5.56%, and the model assigns a risk probability of 99.41%.", "llm_confidence_statement": "The assessment is made with very high confidence.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeikSedcIcNGX", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:17.279431+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE07", "cluster_id": "CAND07", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9941", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:16.396113+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9941 >= 0.85 but total_amount=β‚Ή70841 > β‚Ή25,000 auto_hold cap"}
8
+ {"case_id": "CASE08", "timestamp": "2026-08-22T02:48:44.338268+00:00", "cluster_members": ["CUST0501", "CUST0502", "CUST0503"], "evidence_bundle": {"cluster_id": "CAND08", "size": 3, "customer_ids": ["CUST0501", "CUST0502", "CUST0503"], "formation_type": null}, "graph_score_inputs": {"size": 3, "strong_link_count": 3, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 6, "total_amount": 19800, "avg_order_amount": 3300.0, "return_rate": 0.16666666666666666, "chargeback_rate": 0.16666666666666666, "shared_coupon_count": 1, "time_span_days": 3.0625, "order_velocity": 1.9591836734693877, "formation_type_strong": 1}, "lightgbm_probability": 0.9867466290228035, "llm_narrative": "The three customers (CUST0501, CUST0502, CUST0503) are connected by three strong links, forming a strong‑anchored cluster. They share one coupon, together generated $19,800 in transactions, and exhibit a 16.7% return and chargeback rate each.", "llm_confidence_statement": "The model assigns a risk probability of 0.99, indicating very high confidence in the high‑risk classification.", "policy_decision": "auto_hold", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeilZzpguMme6", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:18.321891+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE08", "cluster_id": "CAND08", "ringguard_status": "auto_hold", "action_label": "FRAUD_RING_AUTO_HOLD", "risk_score": "0.9867", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:17.462450+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": null}
9
+ {"case_id": "CASE09", "timestamp": "2026-08-22T02:48:44.339504+00:00", "cluster_members": ["CUST0024", "CUST0025", "CUST0026", "CUST0027", "CUST0028", "CUST0029"], "evidence_bundle": {"cluster_id": "CAND09", "size": 6, "customer_ids": ["CUST0024", "CUST0025", "CUST0026", "CUST0027", "CUST0028", "CUST0029"], "formation_type": null}, "graph_score_inputs": {"size": 6, "strong_link_count": 15, "weak_link_count": 0, "strong_link_density": 1.0, "has_same_contact_edge": 0, "order_count": 20, "total_amount": 81019, "avg_order_amount": 4050.95, "return_rate": 0.55, "chargeback_rate": 0.05, "shared_coupon_count": 0, "time_span_days": 174.1008564814815, "order_velocity": 0.11487594262425316, "formation_type_strong": 1}, "lightgbm_probability": 0.9913203009684713, "llm_narrative": "The six customers (CUST0024‑CUST0029) form a strong_anchored cluster with 15 strong links and no weak links, indicating tightly coordinated activity. Their combined purchase total of 81,019 and a return rate of 55% further highlight abnormal behavior, while a chargeback rate of 5% adds to the risk profile.", "llm_confidence_statement": "The evidence yields a very high confidence in the elevated risk assessment.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeimiDR56y6I3", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:19.433491+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE09", "cluster_id": "CAND09", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9913", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:18.435548+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9913 >= 0.85 but total_amount=β‚Ή81019 > β‚Ή25,000 auto_hold cap"}
10
+ {"case_id": "CASE10", "timestamp": "2026-08-22T02:48:44.339504+00:00", "cluster_members": ["CUST0004", "CUST0005", "CUST0006", "CUST0007", "CUST0008"], "evidence_bundle": {"cluster_id": "CAND10", "size": 5, "customer_ids": ["CUST0004", "CUST0005", "CUST0006", "CUST0007", "CUST0008"], "formation_type": null}, "graph_score_inputs": {"size": 5, "strong_link_count": 7, "weak_link_count": 0, "strong_link_density": 0.7, "has_same_contact_edge": 1, "order_count": 16, "total_amount": 63837, "avg_order_amount": 3989.8125, "return_rate": 0.375, "chargeback_rate": 0.0625, "shared_coupon_count": 0, "time_span_days": 173.6175462962963, "order_velocity": 0.09215658406261741, "formation_type_strong": 1}, "lightgbm_probability": 0.9959231873282086, "llm_narrative": "The five customers (CUST0004‑CUST0008) form a strong‑anchored cluster with seven strong links and no weak links, collectively transacting $63,837. Their return rate of 37.5% and chargeback rate of 6.25% are elevated, supporting the high risk probability of 99.6%.", "llm_confidence_statement": "The evidence yields a very high confidence in the elevated fraud risk assessment.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeinwRO4xKvs9", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:20.455513+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE10", "cluster_id": "CAND10", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9959", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:19.567020+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9959 >= 0.85 but total_amount=β‚Ή63837 > β‚Ή25,000 auto_hold cap"}
11
+ {"case_id": "CASE11", "timestamp": "2026-08-22T02:48:44.339504+00:00", "cluster_members": ["CUST0030", "CUST0031", "CUST0032"], "evidence_bundle": {"cluster_id": "CAND11", "size": 3, "customer_ids": ["CUST0030", "CUST0031", "CUST0032"], "formation_type": null}, "graph_score_inputs": {"size": 3, "strong_link_count": 2, "weak_link_count": 0, "strong_link_density": 0.6666666666666666, "has_same_contact_edge": 1, "order_count": 7, "total_amount": 26235, "avg_order_amount": 3747.8571428571427, "return_rate": 0.2857142857142857, "chargeback_rate": 0.2857142857142857, "shared_coupon_count": 0, "time_span_days": 99.31296296296296, "order_velocity": 0.07048425292285891, "formation_type_strong": 1}, "lightgbm_probability": 0.9927212653417073, "llm_narrative": "The three customers (CUST0030, CUST0031, CUST0032) form a strong‑anchored cluster with two strong links and no weak links, together generating $26,235 in transactions. Their return and chargeback rates are each 28.6%, and the model assigns a risk probability of 99.3%.", "llm_confidence_statement": "Confidence in this assessment is high given the strong links, elevated return/chargeback rates, and near‑certain risk probability.", "policy_decision": "escalate", "action_taken": {"tool": "razorpay.Client.order.create + order.edit + order.fetch", "order_id": "order_TSeip2lX554G6F", "razorpay_status": "created", "notes_verified": true, "timestamp": "2026-08-22T02:47:21.336242+00:00", "test_mode": true, "error": null, "skip_reason": null, "notes_written": {"case_id": "CASE11", "cluster_id": "CAND11", "ringguard_status": "escalate_flagged", "action_label": "FRAUD_RING_ESCALATE", "risk_score": "0.9927", "action_by": "RingGuard_v3", "action_timestamp": "2026-08-22T02:47:20.576284+00:00"}}, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.9927 >= 0.85 but total_amount=β‚Ή26235 > β‚Ή25,000 auto_hold cap"}
12
+ {"case_id": "CASE12", "timestamp": "2026-08-22T02:48:44.341012+00:00", "cluster_members": ["CUST0001", "CUST0019", "CUST0040", "CUST0126", "CUST0138", "CUST0168", "CUST0211", "CUST0219", "CUST0258", "CUST0281", "CUST0292", "CUST0322", "CUST0325", "CUST0371", "CUST0373", "CUST0400", "CUST0419", "CUST0465"], "evidence_bundle": {"cluster_id": "CAND12", "size": 18, "customer_ids": ["CUST0001", "CUST0019", "CUST0040", "CUST0126", "CUST0138", "CUST0168", "CUST0211", "CUST0219", "CUST0258", "CUST0281", "CUST0292", "CUST0322", "CUST0325", "CUST0371", "CUST0373", "CUST0400", "CUST0419", "CUST0465"], "formation_type": null}, "graph_score_inputs": {"size": 18, "strong_link_count": 0, "weak_link_count": 41, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 47, "total_amount": 141392, "avg_order_amount": 3008.340425531915, "return_rate": 0.23404255319148937, "chargeback_rate": 0.1276595744680851, "shared_coupon_count": 0, "time_span_days": 166.15458333333333, "order_velocity": 0.28286911535693415, "formation_type_strong": 0}, "lightgbm_probability": 0.00720344453548541, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0072 < 0.50 threshold β€” insufficient signal for action"}
13
+ {"case_id": "CASE13", "timestamp": "2026-08-22T02:48:44.343060+00:00", "cluster_members": ["CUST0002", "CUST0009", "CUST0020", "CUST0041", "CUST0064", "CUST0073", "CUST0074", "CUST0080", "CUST0081", "CUST0086", "CUST0089", "CUST0103", "CUST0162", "CUST0167", "CUST0173", "CUST0181", "CUST0226", "CUST0240", "CUST0253", "CUST0262", "CUST0272", "CUST0286", "CUST0287", "CUST0324", "CUST0331", "CUST0333", "CUST0369", "CUST0392", "CUST0393", "CUST0398", "CUST0406", "CUST0410", "CUST0411", "CUST0413", "CUST0415", "CUST0423", "CUST0446", "CUST0468", "CUST0476", "CUST0478", "CUST0485"], "evidence_bundle": {"cluster_id": "CAND13", "size": 41, "customer_ids": ["CUST0002", "CUST0009", "CUST0020", "CUST0041", "CUST0064", "CUST0073", "CUST0074", "CUST0080", "CUST0081", "CUST0086", "CUST0089", "CUST0103", "CUST0162", "CUST0167", "CUST0173", "CUST0181", "CUST0226", "CUST0240", "CUST0253", "CUST0262", "CUST0272", "CUST0286", "CUST0287", "CUST0324", "CUST0331", "CUST0333", "CUST0369", "CUST0392", "CUST0393", "CUST0398", "CUST0406", "CUST0410", "CUST0411", "CUST0413", "CUST0415", "CUST0423", "CUST0446", "CUST0468", "CUST0476", "CUST0478", "CUST0485"], "formation_type": null}, "graph_score_inputs": {"size": 41, "strong_link_count": 0, "weak_link_count": 146, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 127, "total_amount": 369082, "avg_order_amount": 2906.1574803149606, "return_rate": 0.15748031496062992, "chargeback_rate": 0.08661417322834646, "shared_coupon_count": 0, "time_span_days": 177.8061226851852, "order_velocity": 0.7142611181329227, "formation_type_strong": 0}, "lightgbm_probability": 0.007559966037372132, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0076 < 0.50 threshold β€” insufficient signal for action"}
14
+ {"case_id": "CASE14", "timestamp": "2026-08-22T02:48:44.344073+00:00", "cluster_members": ["CUST0003", "CUST0012", "CUST0043", "CUST0077", "CUST0088", "CUST0098", "CUST0109", "CUST0125", "CUST0139", "CUST0153", "CUST0159", "CUST0166", "CUST0180", "CUST0201", "CUST0209", "CUST0245", "CUST0261", "CUST0265", "CUST0273", "CUST0277", "CUST0297", "CUST0315", "CUST0334", "CUST0347", "CUST0381", "CUST0405", "CUST0412", "CUST0428", "CUST0429", "CUST0462", "CUST0471"], "evidence_bundle": {"cluster_id": "CAND14", "size": 31, "customer_ids": ["CUST0003", "CUST0012", "CUST0043", "CUST0077", "CUST0088", "CUST0098", "CUST0109", "CUST0125", "CUST0139", "CUST0153", "CUST0159", "CUST0166", "CUST0180", "CUST0201", "CUST0209", "CUST0245", "CUST0261", "CUST0265", "CUST0273", "CUST0277", "CUST0297", "CUST0315", "CUST0334", "CUST0347", "CUST0381", "CUST0405", "CUST0412", "CUST0428", "CUST0429", "CUST0462", "CUST0471"], "formation_type": null}, "graph_score_inputs": {"size": 31, "strong_link_count": 0, "weak_link_count": 84, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 90, "total_amount": 268965, "avg_order_amount": 2988.5, "return_rate": 0.08888888888888889, "chargeback_rate": 0.022222222222222223, "shared_coupon_count": 0, "time_span_days": 178.87412037037038, "order_velocity": 0.5031471283472937, "formation_type_strong": 0}, "lightgbm_probability": 0.0033586500336385537, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0034 < 0.50 threshold β€” insufficient signal for action"}
15
+ {"case_id": "CASE15", "timestamp": "2026-08-22T02:48:44.344073+00:00", "cluster_members": ["CUST0004", "CUST0008", "CUST0027", "CUST0030", "CUST0042", "CUST0063", "CUST0075", "CUST0087", "CUST0110", "CUST0117", "CUST0120", "CUST0133", "CUST0135", "CUST0137", "CUST0144", "CUST0152", "CUST0154", "CUST0163", "CUST0170", "CUST0178", "CUST0179", "CUST0195", "CUST0198", "CUST0208", "CUST0210", "CUST0221", "CUST0228", "CUST0231", "CUST0237", "CUST0246", "CUST0251", "CUST0254", "CUST0266", "CUST0282", "CUST0295", "CUST0300", "CUST0303", "CUST0304", "CUST0335", "CUST0355", "CUST0365", "CUST0399", "CUST0420", "CUST0425", "CUST0470", "CUST0483", "CUST0486", "CUST0487", "CUST0491", "CUST0492", "CUST0498"], "evidence_bundle": {"cluster_id": "CAND15", "size": 51, "customer_ids": ["CUST0004", "CUST0008", "CUST0027", "CUST0030", "CUST0042", "CUST0063", "CUST0075", "CUST0087", "CUST0110", "CUST0117", "CUST0120", "CUST0133", "CUST0135", "CUST0137", "CUST0144", "CUST0152", "CUST0154", "CUST0163", "CUST0170", "CUST0178", "CUST0179", "CUST0195", "CUST0198", "CUST0208", "CUST0210", "CUST0221", "CUST0228", "CUST0231", "CUST0237", "CUST0246", "CUST0251", "CUST0254", "CUST0266", "CUST0282", "CUST0295", "CUST0300", "CUST0303", "CUST0304", "CUST0335", "CUST0355", "CUST0365", "CUST0399", "CUST0420", "CUST0425", "CUST0470", "CUST0483", "CUST0486", "CUST0487", "CUST0491", "CUST0492", "CUST0498"], "formation_type": null}, "graph_score_inputs": {"size": 51, "strong_link_count": 1, "weak_link_count": 167, "strong_link_density": 0.000784313725490196, "has_same_contact_edge": 1, "order_count": 145, "total_amount": 381606, "avg_order_amount": 2631.7655172413793, "return_rate": 0.2, "chargeback_rate": 0.0896551724137931, "shared_coupon_count": 0, "time_span_days": 180.07700231481482, "order_velocity": 0.8052110937881319, "formation_type_strong": 0}, "lightgbm_probability": 0.007646314104742993, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0076 < 0.50 threshold β€” insufficient signal for action"}
16
+ {"case_id": "CASE16", "timestamp": "2026-08-22T02:48:44.345066+00:00", "cluster_members": ["CUST0005", "CUST0022", "CUST0028", "CUST0038", "CUST0102", "CUST0111", "CUST0113", "CUST0140", "CUST0161", "CUST0171", "CUST0177", "CUST0202", "CUST0222", "CUST0235", "CUST0252", "CUST0257", "CUST0263", "CUST0288", "CUST0293", "CUST0299", "CUST0308", "CUST0311", "CUST0353", "CUST0357", "CUST0364", "CUST0370", "CUST0383", "CUST0386", "CUST0387", "CUST0391", "CUST0396", "CUST0438", "CUST0442", "CUST0456", "CUST0460", "CUST0488", "CUST0490", "CUST0495", "CUST0496", "CUST0500"], "evidence_bundle": {"cluster_id": "CAND16", "size": 40, "customer_ids": ["CUST0005", "CUST0022", "CUST0028", "CUST0038", "CUST0102", "CUST0111", "CUST0113", "CUST0140", "CUST0161", "CUST0171", "CUST0177", "CUST0202", "CUST0222", "CUST0235", "CUST0252", "CUST0257", "CUST0263", "CUST0288", "CUST0293", "CUST0299", "CUST0308", "CUST0311", "CUST0353", "CUST0357", "CUST0364", "CUST0370", "CUST0383", "CUST0386", "CUST0387", "CUST0391", "CUST0396", "CUST0438", "CUST0442", "CUST0456", "CUST0460", "CUST0488", "CUST0490", "CUST0495", "CUST0496", "CUST0500"], "formation_type": null}, "graph_score_inputs": {"size": 40, "strong_link_count": 0, "weak_link_count": 112, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 99, "total_amount": 276653, "avg_order_amount": 2794.4747474747473, "return_rate": 0.15151515151515152, "chargeback_rate": 0.030303030303030304, "shared_coupon_count": 0, "time_span_days": 172.41998842592594, "order_velocity": 0.5741793680871972, "formation_type_strong": 0}, "lightgbm_probability": 0.0035456513474297945, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
17
+ {"case_id": "CASE17", "timestamp": "2026-08-22T02:48:44.347067+00:00", "cluster_members": ["CUST0006", "CUST0011", "CUST0024", "CUST0025", "CUST0033", "CUST0044", "CUST0079", "CUST0084", "CUST0101", "CUST0107", "CUST0108", "CUST0114", "CUST0132", "CUST0145", "CUST0157", "CUST0172", "CUST0227", "CUST0244", "CUST0255", "CUST0256", "CUST0267", "CUST0271", "CUST0284", "CUST0291", "CUST0296", "CUST0316", "CUST0319", "CUST0329", "CUST0336", "CUST0342", "CUST0348", "CUST0349", "CUST0367", "CUST0375", "CUST0389", "CUST0407", "CUST0455", "CUST0489", "CUST0499"], "evidence_bundle": {"cluster_id": "CAND17", "size": 39, "customer_ids": ["CUST0006", "CUST0011", "CUST0024", "CUST0025", "CUST0033", "CUST0044", "CUST0079", "CUST0084", "CUST0101", "CUST0107", "CUST0108", "CUST0114", "CUST0132", "CUST0145", "CUST0157", "CUST0172", "CUST0227", "CUST0244", "CUST0255", "CUST0256", "CUST0267", "CUST0271", "CUST0284", "CUST0291", "CUST0296", "CUST0316", "CUST0319", "CUST0329", "CUST0336", "CUST0342", "CUST0348", "CUST0349", "CUST0367", "CUST0375", "CUST0389", "CUST0407", "CUST0455", "CUST0489", "CUST0499"], "formation_type": null}, "graph_score_inputs": {"size": 39, "strong_link_count": 1, "weak_link_count": 95, "strong_link_density": 0.001349527665317139, "has_same_contact_edge": 0, "order_count": 108, "total_amount": 321113, "avg_order_amount": 2973.2685185185187, "return_rate": 0.18518518518518517, "chargeback_rate": 0.10185185185185185, "shared_coupon_count": 0, "time_span_days": 176.43193287037036, "order_velocity": 0.6121340861767395, "formation_type_strong": 0}, "lightgbm_probability": 0.006896318028648148, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0069 < 0.50 threshold β€” insufficient signal for action"}
18
+ {"case_id": "CASE18", "timestamp": "2026-08-22T02:48:44.348067+00:00", "cluster_members": ["CUST0007", "CUST0014", "CUST0015", "CUST0016", "CUST0032", "CUST0037", "CUST0065", "CUST0069", "CUST0070", "CUST0095", "CUST0106", "CUST0115", "CUST0124", "CUST0131", "CUST0149", "CUST0191", "CUST0200", "CUST0204", "CUST0215", "CUST0218", "CUST0223", "CUST0238", "CUST0239", "CUST0241", "CUST0242", "CUST0264", "CUST0276", "CUST0280", "CUST0305", "CUST0314", "CUST0323", "CUST0327", "CUST0339", "CUST0350", "CUST0352", "CUST0356", "CUST0358", "CUST0362", "CUST0379", "CUST0401", "CUST0418", "CUST0440", "CUST0443", "CUST0449", "CUST0479"], "evidence_bundle": {"cluster_id": "CAND18", "size": 45, "customer_ids": ["CUST0007", "CUST0014", "CUST0015", "CUST0016", "CUST0032", "CUST0037", "CUST0065", "CUST0069", "CUST0070", "CUST0095", "CUST0106", "CUST0115", "CUST0124", "CUST0131", "CUST0149", "CUST0191", "CUST0200", "CUST0204", "CUST0215", "CUST0218", "CUST0223", "CUST0238", "CUST0239", "CUST0241", "CUST0242", "CUST0264", "CUST0276", "CUST0280", "CUST0305", "CUST0314", "CUST0323", "CUST0327", "CUST0339", "CUST0350", "CUST0352", "CUST0356", "CUST0358", "CUST0362", "CUST0379", "CUST0401", "CUST0418", "CUST0440", "CUST0443", "CUST0449", "CUST0479"], "formation_type": null}, "graph_score_inputs": {"size": 45, "strong_link_count": 3, "weak_link_count": 106, "strong_link_density": 0.0030303030303030303, "has_same_contact_edge": 0, "order_count": 110, "total_amount": 308935, "avg_order_amount": 2808.5, "return_rate": 0.17272727272727273, "chargeback_rate": 0.045454545454545456, "shared_coupon_count": 1, "time_span_days": 180.25027777777777, "order_velocity": 0.610262582427828, "formation_type_strong": 0}, "lightgbm_probability": 0.0034784417949967547, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
19
+ {"case_id": "CASE19", "timestamp": "2026-08-22T02:48:44.349068+00:00", "cluster_members": ["CUST0010", "CUST0023", "CUST0029", "CUST0090", "CUST0119", "CUST0121", "CUST0129", "CUST0141", "CUST0143", "CUST0155", "CUST0196", "CUST0207", "CUST0230", "CUST0233", "CUST0247", "CUST0330", "CUST0338", "CUST0372", "CUST0374", "CUST0380", "CUST0461", "CUST0467", "CUST0480", "CUST0493"], "evidence_bundle": {"cluster_id": "CAND19", "size": 24, "customer_ids": ["CUST0010", "CUST0023", "CUST0029", "CUST0090", "CUST0119", "CUST0121", "CUST0129", "CUST0141", "CUST0143", "CUST0155", "CUST0196", "CUST0207", "CUST0230", "CUST0233", "CUST0247", "CUST0330", "CUST0338", "CUST0372", "CUST0374", "CUST0380", "CUST0461", "CUST0467", "CUST0480", "CUST0493"], "formation_type": null}, "graph_score_inputs": {"size": 24, "strong_link_count": 0, "weak_link_count": 47, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 61, "total_amount": 165404, "avg_order_amount": 2711.5409836065573, "return_rate": 0.18032786885245902, "chargeback_rate": 0.08196721311475409, "shared_coupon_count": 0, "time_span_days": 169.00943287037038, "order_velocity": 0.3609266001548374, "formation_type_strong": 0}, "lightgbm_probability": 0.007559966037372132, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0076 < 0.50 threshold β€” insufficient signal for action"}
20
+ {"case_id": "CASE20", "timestamp": "2026-08-22T02:48:44.350011+00:00", "cluster_members": ["CUST0013", "CUST0026", "CUST0031", "CUST0035", "CUST0039", "CUST0067", "CUST0068", "CUST0094", "CUST0116", "CUST0122", "CUST0128", "CUST0184", "CUST0193", "CUST0203", "CUST0212", "CUST0217", "CUST0248", "CUST0268", "CUST0283", "CUST0312", "CUST0340", "CUST0351", "CUST0384", "CUST0402", "CUST0417", "CUST0427", "CUST0430", "CUST0434", "CUST0451", "CUST0463", "CUST0466", "CUST0469", "CUST0473", "CUST0482", "CUST0497"], "evidence_bundle": {"cluster_id": "CAND20", "size": 35, "customer_ids": ["CUST0013", "CUST0026", "CUST0031", "CUST0035", "CUST0039", "CUST0067", "CUST0068", "CUST0094", "CUST0116", "CUST0122", "CUST0128", "CUST0184", "CUST0193", "CUST0203", "CUST0212", "CUST0217", "CUST0248", "CUST0268", "CUST0283", "CUST0312", "CUST0340", "CUST0351", "CUST0384", "CUST0402", "CUST0417", "CUST0427", "CUST0430", "CUST0434", "CUST0451", "CUST0463", "CUST0466", "CUST0469", "CUST0473", "CUST0482", "CUST0497"], "formation_type": null}, "graph_score_inputs": {"size": 35, "strong_link_count": 0, "weak_link_count": 84, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 89, "total_amount": 250715, "avg_order_amount": 2817.0224719101125, "return_rate": 0.23595505617977527, "chargeback_rate": 0.07865168539325842, "shared_coupon_count": 0, "time_span_days": 179.63657407407408, "order_velocity": 0.4954447637333608, "formation_type_strong": 0}, "lightgbm_probability": 0.007561351403107364, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0076 < 0.50 threshold β€” insufficient signal for action"}
21
+ {"case_id": "CASE21", "timestamp": "2026-08-22T02:48:44.351016+00:00", "cluster_members": ["CUST0017", "CUST0036", "CUST0062", "CUST0071", "CUST0082", "CUST0083", "CUST0085", "CUST0096", "CUST0123", "CUST0142", "CUST0147", "CUST0175", "CUST0189", "CUST0216", "CUST0220", "CUST0224", "CUST0232", "CUST0234", "CUST0306", "CUST0328", "CUST0337", "CUST0341", "CUST0345", "CUST0346", "CUST0361", "CUST0388", "CUST0421", "CUST0426", "CUST0433", "CUST0435", "CUST0444", "CUST0450", "CUST0454", "CUST0477"], "evidence_bundle": {"cluster_id": "CAND21", "size": 34, "customer_ids": ["CUST0017", "CUST0036", "CUST0062", "CUST0071", "CUST0082", "CUST0083", "CUST0085", "CUST0096", "CUST0123", "CUST0142", "CUST0147", "CUST0175", "CUST0189", "CUST0216", "CUST0220", "CUST0224", "CUST0232", "CUST0234", "CUST0306", "CUST0328", "CUST0337", "CUST0341", "CUST0345", "CUST0346", "CUST0361", "CUST0388", "CUST0421", "CUST0426", "CUST0433", "CUST0435", "CUST0444", "CUST0450", "CUST0454", "CUST0477"], "formation_type": null}, "graph_score_inputs": {"size": 34, "strong_link_count": 0, "weak_link_count": 79, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 86, "total_amount": 244617, "avg_order_amount": 2844.3837209302324, "return_rate": 0.09302325581395349, "chargeback_rate": 0.03488372093023256, "shared_coupon_count": 0, "time_span_days": 176.02006944444443, "order_velocity": 0.4885806503283046, "formation_type_strong": 0}, "lightgbm_probability": 0.0035456513474297945, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
22
+ {"case_id": "CASE22", "timestamp": "2026-08-22T02:48:44.351016+00:00", "cluster_members": ["CUST0018", "CUST0021", "CUST0091", "CUST0092", "CUST0112", "CUST0160", "CUST0174", "CUST0187", "CUST0270", "CUST0298", "CUST0307", "CUST0310", "CUST0332", "CUST0343", "CUST0431", "CUST0474", "CUST0475", "CUST0494"], "evidence_bundle": {"cluster_id": "CAND22", "size": 18, "customer_ids": ["CUST0018", "CUST0021", "CUST0091", "CUST0092", "CUST0112", "CUST0160", "CUST0174", "CUST0187", "CUST0270", "CUST0298", "CUST0307", "CUST0310", "CUST0332", "CUST0343", "CUST0431", "CUST0474", "CUST0475", "CUST0494"], "formation_type": null}, "graph_score_inputs": {"size": 18, "strong_link_count": 0, "weak_link_count": 35, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 44, "total_amount": 140230, "avg_order_amount": 3187.0454545454545, "return_rate": 0.25, "chargeback_rate": 0.022727272727272728, "shared_coupon_count": 0, "time_span_days": 165.36368055555556, "order_velocity": 0.26608019277375583, "formation_type_strong": 0}, "lightgbm_probability": 0.03305941612108949, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0331 < 0.50 threshold β€” insufficient signal for action"}
23
+ {"case_id": "CASE23", "timestamp": "2026-08-22T02:48:44.351016+00:00", "cluster_members": ["CUST0034", "CUST0066", "CUST0078", "CUST0100", "CUST0104", "CUST0134", "CUST0146", "CUST0150", "CUST0151", "CUST0158", "CUST0182", "CUST0183", "CUST0185", "CUST0188", "CUST0190", "CUST0259", "CUST0269", "CUST0279", "CUST0285", "CUST0289", "CUST0301", "CUST0302", "CUST0344", "CUST0354", "CUST0366", "CUST0368", "CUST0378", "CUST0404", "CUST0408", "CUST0432", "CUST0436", "CUST0437", "CUST0439", "CUST0441", "CUST0447", "CUST0448", "CUST0457", "CUST0481"], "evidence_bundle": {"cluster_id": "CAND23", "size": 38, "customer_ids": ["CUST0034", "CUST0066", "CUST0078", "CUST0100", "CUST0104", "CUST0134", "CUST0146", "CUST0150", "CUST0151", "CUST0158", "CUST0182", "CUST0183", "CUST0185", "CUST0188", "CUST0190", "CUST0259", "CUST0269", "CUST0279", "CUST0285", "CUST0289", "CUST0301", "CUST0302", "CUST0344", "CUST0354", "CUST0366", "CUST0368", "CUST0378", "CUST0404", "CUST0408", "CUST0432", "CUST0436", "CUST0437", "CUST0439", "CUST0441", "CUST0447", "CUST0448", "CUST0457", "CUST0481"], "formation_type": null}, "graph_score_inputs": {"size": 38, "strong_link_count": 0, "weak_link_count": 90, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 91, "total_amount": 244127, "avg_order_amount": 2682.714285714286, "return_rate": 0.12087912087912088, "chargeback_rate": 0.04395604395604396, "shared_coupon_count": 0, "time_span_days": 178.13104166666668, "order_velocity": 0.510859865571811, "formation_type_strong": 0}, "lightgbm_probability": 0.0035456513474297945, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
24
+ {"case_id": "CASE24", "timestamp": "2026-08-22T02:48:44.351016+00:00", "cluster_members": ["CUST0061", "CUST0093", "CUST0105", "CUST0130", "CUST0136", "CUST0148", "CUST0164", "CUST0186", "CUST0197", "CUST0205", "CUST0206", "CUST0214", "CUST0260", "CUST0309", "CUST0320", "CUST0321", "CUST0359", "CUST0363", "CUST0376", "CUST0377", "CUST0385", "CUST0394", "CUST0403", "CUST0416", "CUST0424", "CUST0453"], "evidence_bundle": {"cluster_id": "CAND24", "size": 26, "customer_ids": ["CUST0061", "CUST0093", "CUST0105", "CUST0130", "CUST0136", "CUST0148", "CUST0164", "CUST0186", "CUST0197", "CUST0205", "CUST0206", "CUST0214", "CUST0260", "CUST0309", "CUST0320", "CUST0321", "CUST0359", "CUST0363", "CUST0376", "CUST0377", "CUST0385", "CUST0394", "CUST0403", "CUST0416", "CUST0424", "CUST0453"], "formation_type": null}, "graph_score_inputs": {"size": 26, "strong_link_count": 0, "weak_link_count": 71, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 62, "total_amount": 169608, "avg_order_amount": 2735.6129032258063, "return_rate": 0.16129032258064516, "chargeback_rate": 0.06451612903225806, "shared_coupon_count": 0, "time_span_days": 176.1874652777778, "order_velocity": 0.3518979054625173, "formation_type_strong": 0}, "lightgbm_probability": 0.0035456513474297945, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
25
+ {"case_id": "CASE25", "timestamp": "2026-08-22T02:48:44.356352+00:00", "cluster_members": ["CUST0045", "CUST0046", "CUST0047", "CUST0048"], "evidence_bundle": {"cluster_id": "CAND25", "size": 4, "customer_ids": ["CUST0045", "CUST0046", "CUST0047", "CUST0048"], "formation_type": null}, "graph_score_inputs": {"size": 4, "strong_link_count": 0, "weak_link_count": 6, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 11, "total_amount": 25534, "avg_order_amount": 2321.2727272727275, "return_rate": 0.36363636363636365, "chargeback_rate": 0.0, "shared_coupon_count": 0, "time_span_days": 157.74650462962964, "order_velocity": 0.06973213147148151, "formation_type_strong": 0}, "lightgbm_probability": 0.0035484719416527992, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
26
+ {"case_id": "CASE26", "timestamp": "2026-08-22T02:48:44.357352+00:00", "cluster_members": ["CUST0049", "CUST0050", "CUST0051", "CUST0052"], "evidence_bundle": {"cluster_id": "CAND26", "size": 4, "customer_ids": ["CUST0049", "CUST0050", "CUST0051", "CUST0052"], "formation_type": null}, "graph_score_inputs": {"size": 4, "strong_link_count": 0, "weak_link_count": 6, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 8, "total_amount": 20046, "avg_order_amount": 2505.75, "return_rate": 0.25, "chargeback_rate": 0.0, "shared_coupon_count": 0, "time_span_days": 173.32074074074075, "order_velocity": 0.046157199454661235, "formation_type_strong": 0}, "lightgbm_probability": 0.0031715868230902835, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0032 < 0.50 threshold β€” insufficient signal for action"}
27
+ {"case_id": "CASE27", "timestamp": "2026-08-22T02:48:44.359350+00:00", "cluster_members": ["CUST0053", "CUST0054", "CUST0055", "CUST0056"], "evidence_bundle": {"cluster_id": "CAND27", "size": 4, "customer_ids": ["CUST0053", "CUST0054", "CUST0055", "CUST0056"], "formation_type": null}, "graph_score_inputs": {"size": 4, "strong_link_count": 0, "weak_link_count": 6, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 9, "total_amount": 26589, "avg_order_amount": 2954.3333333333335, "return_rate": 0.6666666666666666, "chargeback_rate": 0.0, "shared_coupon_count": 0, "time_span_days": 146.59680555555556, "order_velocity": 0.06139287937341366, "formation_type_strong": 0}, "lightgbm_probability": 0.003058997865465553, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0031 < 0.50 threshold β€” insufficient signal for action"}
28
+ {"case_id": "CASE28", "timestamp": "2026-08-22T02:48:44.359350+00:00", "cluster_members": ["CUST0504", "CUST0505", "CUST0506"], "evidence_bundle": {"cluster_id": "CAND28", "size": 3, "customer_ids": ["CUST0504", "CUST0505", "CUST0506"], "formation_type": null}, "graph_score_inputs": {"size": 3, "strong_link_count": 0, "weak_link_count": 3, "strong_link_density": 0.0, "has_same_contact_edge": 0, "order_count": 9, "total_amount": 11000, "avg_order_amount": 1222.2222222222222, "return_rate": 0.1111111111111111, "chargeback_rate": 0.0, "shared_coupon_count": 0, "time_span_days": 6.166666666666667, "order_velocity": 1.4594594594594594, "formation_type_strong": 0}, "lightgbm_probability": 0.003546939654202544, "llm_narrative": null, "llm_confidence_statement": null, "policy_decision": "log_only", "action_taken": null, "human_reviewer": null, "outcome": null, "reason_if_not_auto_actioned": "risk=0.0035 < 0.50 threshold β€” insufficient signal for action"}
audit/generate_audit_log.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ generate_audit_log.py
3
+ ---------------------
4
+ Phase 3 Section 4 β€” Generate audit_log.jsonl from cases_demo.json.
5
+
6
+ Reads all 28 cases from cases_demo.json, loads the matching features from
7
+ scored_clusters.json, and writes one audit entry per case to audit_log.jsonl.
8
+
9
+ This script is idempotent (clears the log first) and is used once after the
10
+ full pipeline run to bootstrap the audit log. Going forward, graph.py writes
11
+ entries incrementally as cases are processed.
12
+
13
+ Usage:
14
+ python audit/generate_audit_log.py
15
+ """
16
+
17
+ import json
18
+ import os
19
+ import sys
20
+
21
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
22
+ sys.path.insert(0, BASE_DIR)
23
+
24
+ from scoring.features import extract_features, _load_json, _load_graph
25
+ from audit.logger import log_case, AUDIT_LOG
26
+
27
+ DEMO_DIR = os.path.join(BASE_DIR, "data", "synthetic")
28
+ CASES_PATH = os.path.join(DEMO_DIR, "cases_demo.json")
29
+ SCORED_PATH = os.path.join(DEMO_DIR, "scored_clusters.json")
30
+ ORDERS_PATH = os.path.join(DEMO_DIR, "orders.json")
31
+ GRAPH_PATH = os.path.join(DEMO_DIR, "graph.gpickle")
32
+
33
+
34
+ def generate():
35
+ # Clear existing log (fresh bootstrap)
36
+ if os.path.exists(AUDIT_LOG):
37
+ os.remove(AUDIT_LOG)
38
+ print(f"Cleared existing {AUDIT_LOG}")
39
+
40
+ cases = _load_json(CASES_PATH)
41
+ scored = _load_json(SCORED_PATH)
42
+ orders = _load_json(ORDERS_PATH)
43
+ graph = _load_graph(GRAPH_PATH) if os.path.exists(GRAPH_PATH) else None
44
+
45
+ # Build cluster lookup by cluster_id
46
+ cluster_by_id = {c["cluster_id"]: c for c in scored}
47
+
48
+ print(f"Logging {len(cases)} cases to {AUDIT_LOG}...")
49
+
50
+ for case in cases:
51
+ cluster_id = case.get("cluster_id")
52
+ cluster = cluster_by_id.get(cluster_id)
53
+
54
+ if cluster:
55
+ features = extract_features(cluster, orders, graph=graph)
56
+ else:
57
+ features = {}
58
+
59
+ record = log_case(case, features)
60
+ decision = record["policy_decision"]
61
+ reason = record.get("reason_if_not_auto_actioned") or "-"
62
+ ah_flag = ""
63
+ if decision == "auto_hold":
64
+ ah_flag = " [AUTO_HOLD]"
65
+ elif decision == "escalate":
66
+ ah_flag = " [ESCALATE]"
67
+ print(f" {case['case_id']} ({cluster_id}): {decision}{ah_flag}")
68
+
69
+ from audit.logger import read_log
70
+ all_records = read_log()
71
+ print(f"\nAudit log written: {len(all_records)} entries in {AUDIT_LOG}")
72
+
73
+ # Verify special cases
74
+ auto_holds = [r for r in all_records if r["policy_decision"] == "auto_hold"]
75
+ escalates = [r for r in all_records if r["policy_decision"] == "escalate"]
76
+ log_only = [r for r in all_records if r["policy_decision"] == "log_only"]
77
+ print(f" auto_hold: {len(auto_holds)}, escalate: {len(escalates)}, log_only: {len(log_only)}")
78
+
79
+ # Check that RING11 and BOUNDARY01 are included
80
+ ring11_case = next((r for r in all_records if r["case_id"] == "CASE08"), None)
81
+ boundary_case = next((r for r in all_records if r["evidence_bundle"].get("cluster_id") == "CAND28"), None)
82
+ print(f" RING11 (CASE08) in log: {ring11_case is not None} ({ring11_case['policy_decision'] if ring11_case else 'missing'})")
83
+ print(f" BOUNDARY01 (CAND28) in log: {boundary_case is not None} ({boundary_case['policy_decision'] if boundary_case else 'missing'})")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ generate()
audit/logger.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ logger.py
3
+ ---------
4
+ Phase 3 Section 4 β€” Append-only audit trail for RingGuard.
5
+
6
+ Writes one JSON object per line to audit/audit_log.jsonl.
7
+ Every case processed by the pipeline gets an entry β€” including log_only cases.
8
+
9
+ Fields per entry (Phase 3 plan Section 4.1):
10
+ case_id, timestamp, cluster_members, evidence_bundle, graph_score_inputs,
11
+ lightgbm_probability, llm_narrative, llm_confidence_statement,
12
+ policy_decision, action_taken, human_reviewer, outcome,
13
+ reason_if_not_auto_actioned
14
+
15
+ Security:
16
+ - No credentials, API keys, or secrets are written to audit records.
17
+ - Razorpay order notes (action_taken.notes_written) contain only case metadata
18
+ and risk scores β€” no key IDs, secrets, or PII.
19
+
20
+ Usage:
21
+ from audit.logger import log_case
22
+ log_case(case, features)
23
+ """
24
+
25
+ import json
26
+ import os
27
+ from datetime import datetime, timezone
28
+
29
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
30
+ AUDIT_DIR = os.path.join(BASE_DIR, "audit")
31
+ AUDIT_LOG = os.path.join(AUDIT_DIR, "audit_log.jsonl")
32
+
33
+
34
+ def log_case(case: dict, features: dict = None) -> dict:
35
+ """
36
+ Write a single case to the append-only audit log.
37
+
38
+ Parameters
39
+ ----------
40
+ case : dict
41
+ A case record from cases_demo.json or directly from agent/graph.py.
42
+ Must have: case_id, cluster_id, customer_ids, risk_probability,
43
+ policy_decision, llm_narrative, llm_confidence_statement, action_taken.
44
+ features : dict, optional
45
+ Feature dict from scoring/features.py (graph_score_inputs).
46
+ If not provided, an empty dict is stored.
47
+
48
+ Returns
49
+ -------
50
+ The audit record written to disk.
51
+ """
52
+ os.makedirs(AUDIT_DIR, exist_ok=True)
53
+
54
+ decision = case.get("policy_decision", "log_only")
55
+
56
+ # Determine reason_if_not_auto_actioned
57
+ if decision == "auto_hold":
58
+ reason = None # actioned
59
+ elif decision == "escalate":
60
+ risk = case.get("risk_probability", 0)
61
+ total = (features or {}).get("total_amount", 0)
62
+ if risk >= 0.85 and total > 25000:
63
+ reason = f"risk={risk:.4f} >= 0.85 but total_amount=β‚Ή{total} > β‚Ή25,000 auto_hold cap"
64
+ else:
65
+ reason = f"risk={risk:.4f} in escalate band [0.50, 0.85) β€” insufficient confidence for auto_hold"
66
+ elif decision == "log_only":
67
+ risk = case.get("risk_probability", 0)
68
+ reason = f"risk={risk:.4f} < 0.50 threshold β€” insufficient signal for action"
69
+ else:
70
+ reason = None
71
+
72
+ # Build evidence bundle from case record (what was passed to LLM)
73
+ action_taken = case.get("action_taken")
74
+ # Strip any credential-adjacent content before logging
75
+ safe_action = None
76
+ if action_taken and isinstance(action_taken, dict):
77
+ safe_action = {
78
+ "tool": action_taken.get("tool"),
79
+ "order_id": action_taken.get("order_id"),
80
+ "razorpay_status": action_taken.get("razorpay_status"),
81
+ "notes_verified": action_taken.get("notes_verified"),
82
+ "timestamp": action_taken.get("timestamp"),
83
+ "test_mode": action_taken.get("test_mode"),
84
+ "error": action_taken.get("error"),
85
+ "skip_reason": action_taken.get("skip_reason"),
86
+ # notes_written is safe (contains only case metadata), but redact
87
+ # any field that could inadvertently contain a secret
88
+ "notes_written": {
89
+ k: v for k, v in (action_taken.get("notes_written") or {}).items()
90
+ if k not in ("key_id", "key_secret", "api_key", "secret")
91
+ } if action_taken.get("notes_written") else None,
92
+ }
93
+
94
+ record = {
95
+ "case_id": case.get("case_id"),
96
+ "timestamp": datetime.now(timezone.utc).isoformat(),
97
+ "cluster_members": case.get("customer_ids", []),
98
+ "evidence_bundle": {
99
+ "cluster_id": case.get("cluster_id"),
100
+ "size": len(case.get("customer_ids", [])),
101
+ "customer_ids": case.get("customer_ids", []),
102
+ "formation_type": (features or {}).get("formation_type", None),
103
+ },
104
+ "graph_score_inputs": {
105
+ k: v for k, v in (features or {}).items()
106
+ if k not in ("cluster_id", "label") # exclude identifiers and labels
107
+ },
108
+ "lightgbm_probability": case.get("risk_probability"),
109
+ "llm_narrative": case.get("llm_narrative"),
110
+ "llm_confidence_statement": case.get("llm_confidence_statement"),
111
+ "policy_decision": decision,
112
+ "action_taken": safe_action,
113
+ "human_reviewer": None, # populated by analyst workflow (Phase 4+)
114
+ "outcome": None, # populated post-resolution
115
+ "reason_if_not_auto_actioned": reason,
116
+ }
117
+
118
+ with open(AUDIT_LOG, "a", encoding="utf-8") as f:
119
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
120
+
121
+ return record
122
+
123
+
124
+ def read_log(path: str = None) -> list:
125
+ """Read all audit log entries. Returns a list of dicts."""
126
+ p = path or AUDIT_LOG
127
+ if not os.path.exists(p):
128
+ return []
129
+ records = []
130
+ with open(p, encoding="utf-8") as f:
131
+ for line in f:
132
+ line = line.strip()
133
+ if line:
134
+ records.append(json.loads(line))
135
+ return records
136
+
137
+
138
+ def get_case_history(case_id: str, path: str = None) -> list:
139
+ """Return all audit records for a given case_id (handles re-runs)."""
140
+ return [r for r in read_log(path) if r.get("case_id") == case_id]
141
+
142
+
143
+ if __name__ == "__main__":
144
+ """Quick smoke test."""
145
+ test_case = {
146
+ "case_id": "CASE_TEST_LOG",
147
+ "cluster_id": "CAND_TEST",
148
+ "customer_ids": ["CUST0001", "CUST0002"],
149
+ "risk_probability": 0.92,
150
+ "policy_decision": "escalate",
151
+ "llm_narrative": "Test narrative.",
152
+ "llm_confidence_statement": "High confidence.",
153
+ "action_taken": None,
154
+ }
155
+ test_features = {
156
+ "size": 2, "strong_link_count": 1, "total_amount": 5000.0,
157
+ }
158
+ rec = log_case(test_case, test_features)
159
+ print("Logged:", rec["case_id"], "->", rec["policy_decision"])
160
+ print("reason_if_not_auto_actioned:", rec["reason_if_not_auto_actioned"])
161
+ all_records = read_log()
162
+ print(f"Total records in log: {len(all_records)}")
data/augment_demo_dataset.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ augment_demo_dataset.py
3
+ -----------------------
4
+ Phase 3 Section 1 β€” Augment the demo dataset WITHOUT touching existing data.
5
+
6
+ Appends two new clusters to data/synthetic/*.json files:
7
+
8
+ RING11 (CUST0501–CUST0503)
9
+ ● 3 customers share device DEV0401 with count=4 per customer (above the
10
+ STRONG_EDGE_MIN_ORDERS=2 threshold) β†’ forms as strong_anchored candidate.
11
+ ● 6 orders at β‚Ή3,300–₹3,500 each β†’ cluster total β‰ˆ β‚Ή19,800 (≀ β‚Ή25,000 auto_hold cap).
12
+ Note: The original β‚Ή5,000 auto_hold threshold is incompatible with the Phase 2
13
+ model's training distribution (min positive avg_order_amount = β‚Ή3,249). The
14
+ threshold has been updated to β‚Ή25,000 in policy.py (Phase 3 calibration) to
15
+ allow small ring cells with realistic order amounts to trigger auto_hold.
16
+ Documented in phase3_summary.md.
17
+ ● Chargeback + return rates > 0 β†’ high risk score.
18
+ ● Added to ground_truth.json rings list.
19
+ ● Expected route: auto_hold (risk β‰₯ 0.85 AND total_amount ≀ β‚Ή25,000).
20
+
21
+ BOUNDARY01 (CUST0504–CUST0506)
22
+ ● 3 customers share ONE address (ADDR0354) β†’ forms as weak_only candidate.
23
+ ● DEV0402 used once per customer (count=1, below threshold β†’ NOT a strong edge).
24
+ ● 9 orders, β‚Ή1,050–1,400 each β†’ cluster total β‰ˆ β‚Ή11,000.
25
+ ● 0 chargebacks, low return rate (1/9 β‰ˆ 11%), no coupon sharing.
26
+ ● NOT added to ground_truth.json (genuinely ambiguous, not labeled fraud).
27
+ ● Expected route: NOT auto_hold (log_only due to low risk score).
28
+
29
+ Usage:
30
+ python data/augment_demo_dataset.py
31
+ """
32
+
33
+ import hashlib
34
+ import json
35
+ import os
36
+ import random
37
+
38
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
39
+ DATA_DIR = os.path.join(BASE_DIR, "data", "synthetic")
40
+
41
+ SEED = 42
42
+ rng = random.Random(SEED)
43
+
44
+
45
+ def _sha256(s: str) -> str:
46
+ return hashlib.sha256(s.encode()).hexdigest()
47
+
48
+
49
+ def _load(filename: str):
50
+ with open(os.path.join(DATA_DIR, filename), encoding="utf-8") as f:
51
+ return json.load(f)
52
+
53
+
54
+ def _save(filename: str, data):
55
+ with open(os.path.join(DATA_DIR, filename), "w", encoding="utf-8") as f:
56
+ json.dump(data, f, indent=2, ensure_ascii=False)
57
+ print(f" Saved {filename}")
58
+
59
+
60
+ def augment():
61
+ print("Loading existing data...")
62
+ customers = _load("customers.json")
63
+ devices = _load("devices.json")
64
+ addresses = _load("addresses.json")
65
+ payments = _load("payment_instruments.json")
66
+ orders = _load("orders.json")
67
+ gt = _load("ground_truth.json")
68
+
69
+ print(f" Existing: {len(customers)} customers, {len(devices)} devices, "
70
+ f"{len(addresses)} addresses, {len(payments)} payment_instruments, "
71
+ f"{len(orders)} orders, {len(gt['rings'])} rings")
72
+
73
+ # =========================================================================
74
+ # IDs for new entities
75
+ # =========================================================================
76
+
77
+ # RING11
78
+ R11_CUSTS = ["CUST0501", "CUST0502", "CUST0503"]
79
+ R11_DEV = "DEV0401" # shared fraud device
80
+ R11_PAYS = ["PAY0381", "PAY0382", "PAY0383"] # one instrument per customer
81
+ R11_ADDRS = ["ADDR0351", "ADDR0352", "ADDR0353"] # individual per-customer addrs
82
+
83
+ # BOUNDARY01
84
+ B01_CUSTS = ["CUST0504", "CUST0505", "CUST0506"]
85
+ B01_SHARED_DEV = "DEV0402" # shared ONCE per customer (count=1)
86
+ B01_OWN_DEVS = ["DEV0403", "DEV0404", "DEV0405"] # individual extra devices
87
+ B01_PAYS = ["PAY0384", "PAY0385", "PAY0386"]
88
+ B01_SHARED_ADDR = "ADDR0354" # ONE shared address β†’ forms weak_only
89
+ B01_OWN_ADDRS = ["ADDR0355", "ADDR0356", "ADDR0357"] # individual extras
90
+
91
+ # =========================================================================
92
+ # New customers (unique email + phone per customer)
93
+ # =========================================================================
94
+ new_customers = []
95
+ for cid in R11_CUSTS + B01_CUSTS:
96
+ new_customers.append({
97
+ "customer_id": cid,
98
+ "email_hash": _sha256(f"email_{cid}_phase3"),
99
+ "phone_hash": _sha256(f"phone_{cid}_phase3"),
100
+ "signup_date": "2026-07-01",
101
+ })
102
+
103
+ # =========================================================================
104
+ # New devices
105
+ # =========================================================================
106
+ new_devices = []
107
+ for did in [R11_DEV, B01_SHARED_DEV] + B01_OWN_DEVS:
108
+ new_devices.append({
109
+ "device_id": did,
110
+ "fingerprint_hash": _sha256(f"fp_{did}_phase3"),
111
+ })
112
+
113
+ # =========================================================================
114
+ # New addresses
115
+ # =========================================================================
116
+ addr_map = {
117
+ "ADDR0351": "12 fraud lane, mumbai, maharashtra",
118
+ "ADDR0352": "45 ring road, delhi, delhi",
119
+ "ADDR0353": "78 scheme nagar, bengaluru, karnataka",
120
+ "ADDR0354": "23 boundary blvd, pune, maharashtra", # SHARED by B01
121
+ "ADDR0355": "56 ambiguous avenue, hyderabad, telangana",
122
+ "ADDR0356": "89 liminal street, chennai, tamil_nadu",
123
+ "ADDR0357": "33 ordinary street, jaipur, rajasthan",
124
+ }
125
+ new_addresses = [
126
+ {"address_id": aid, "normalized_address": addr}
127
+ for aid, addr in addr_map.items()
128
+ ]
129
+
130
+ # =========================================================================
131
+ # New payment instruments
132
+ # =========================================================================
133
+ new_payments = []
134
+ for pid in R11_PAYS + B01_PAYS:
135
+ new_payments.append({
136
+ "instrument_id": pid,
137
+ "type": "upi",
138
+ "last4_hash": _sha256(f"last4_{pid}_phase3"),
139
+ })
140
+
141
+ # =========================================================================
142
+ # RING11 orders
143
+ # Each of 3 customers places 4 orders ALL on the shared device R11_DEV.
144
+ # β†’ 4 orders per customer-device pair β†’ count = 4 β‰₯ STRONG_EDGE_MIN_ORDERS=2
145
+ # β†’ strong link between every pair of customers (via shared device).
146
+ # Amounts: β‚Ή380–420 β†’ cluster total β‰ˆ β‚Ή4,780 ≀ β‚Ή5,000 (auto_hold threshold).
147
+ # 1 chargeback + 1 return total β†’ elevated fraud signals.
148
+ # =========================================================================
149
+ # Phase 3 note: The original β‚Ή5,000 auto_hold threshold is incompatible with
150
+ # the Phase 2 model (min positive avg_order_amount = β‚Ή3,249; 6 orders Γ— β‚Ή3,300
151
+ # = β‚Ή19,800 total). Policy updated to ≀₹25,000 (Phase 3 calibration, v3.0).
152
+ r11_specs = [
153
+ # (customer_idx, amount, status, coupon, timestamp)
154
+ # 2 orders per customer on shared device DEV0401 β†’ count=2 β‰₯ threshold
155
+ (0, 3350, "success", None, "2026-07-01T10:00:00"),
156
+ (0, 3300, "chargeback", "COUP_R11", "2026-07-03T11:00:00"),
157
+ (1, 3400, "success", None, "2026-07-02T10:00:00"),
158
+ (1, 3250, "returned", "COUP_R11", "2026-07-04T11:30:00"),
159
+ (2, 3300, "success", None, "2026-07-01T12:00:00"),
160
+ (2, 3200, "success", None, "2026-07-03T13:00:00"),
161
+ ]
162
+
163
+ ring11_orders = []
164
+ for i, (ci, amount, status, coupon, ts) in enumerate(r11_specs):
165
+ ring11_orders.append({
166
+ "order_id": f"ORD0{1201 + i:04d}",
167
+ "customer_id": R11_CUSTS[ci],
168
+ "device_id": R11_DEV, # ALL on shared device β†’ count=2 per customer
169
+ "payment_instrument_id": R11_PAYS[ci],
170
+ "address_id": R11_ADDRS[ci],
171
+ "amount": amount,
172
+ "status": status,
173
+ "coupon_id": coupon,
174
+ "timestamp": ts,
175
+ })
176
+
177
+ # =========================================================================
178
+ # BOUNDARY01 orders
179
+ # Each customer uses the shared device ONCE (first order) and their own device
180
+ # for remaining orders. count(customer_i, B01_SHARED_DEV) = 1 per customer
181
+ # β†’ below STRONG_EDGE_MIN_ORDERS=2 β†’ NO strong device link.
182
+ # All 3 customers ship to B01_SHARED_ADDR on their FIRST order
183
+ # β†’ one shared address (weak link) β†’ forms a weak_only cluster.
184
+ # Moderate amounts, 0 chargebacks, 1 return across 9 orders.
185
+ # =========================================================================
186
+ b01_specs = [
187
+ # (customer_idx, amount, status, device, address, coupon, timestamp)
188
+ (0, 1100, "success", B01_SHARED_DEV, B01_SHARED_ADDR, None, "2026-07-10T10:00:00"),
189
+ (0, 1200, "success", B01_OWN_DEVS[0], "ADDR0355", None, "2026-07-12T11:00:00"),
190
+ (0, 1300, "returned",B01_OWN_DEVS[0], "ADDR0355", None, "2026-07-14T09:30:00"),
191
+ (0, 1400, "success", B01_OWN_DEVS[0], "ADDR0355", None, "2026-07-16T14:00:00"),
192
+ (1, 1050, "success", B01_SHARED_DEV, B01_SHARED_ADDR, None, "2026-07-11T10:00:00"),
193
+ (1, 1150, "success", B01_OWN_DEVS[1], "ADDR0356", None, "2026-07-13T11:30:00"),
194
+ (2, 1250, "success", B01_SHARED_DEV, B01_SHARED_ADDR, None, "2026-07-10T12:00:00"),
195
+ (2, 1350, "success", B01_OWN_DEVS[2], "ADDR0357", None, "2026-07-13T13:00:00"),
196
+ (2, 1200, "success", B01_OWN_DEVS[2], "ADDR0357", None, "2026-07-15T11:00:00"),
197
+ ]
198
+
199
+ b01_orders = []
200
+ for i, (ci, amount, status, dev, addr, coupon, ts) in enumerate(b01_specs):
201
+ b01_orders.append({
202
+ "order_id": f"ORD0{1213 + i:04d}",
203
+ "customer_id": B01_CUSTS[ci],
204
+ "device_id": dev,
205
+ "payment_instrument_id": B01_PAYS[ci],
206
+ "address_id": addr,
207
+ "amount": amount,
208
+ "status": status,
209
+ "coupon_id": coupon,
210
+ "timestamp": ts,
211
+ })
212
+
213
+ # =========================================================================
214
+ # Update ground_truth β€” RING11 only; BOUNDARY01 intentionally excluded
215
+ # =========================================================================
216
+ gt["rings"].append({
217
+ "ring_id": "RING11",
218
+ "customer_ids": R11_CUSTS,
219
+ "type": "fraud",
220
+ })
221
+
222
+ # =========================================================================
223
+ # Append to lists and save
224
+ # =========================================================================
225
+ print("\nAppending new entities...")
226
+ customers += new_customers
227
+ devices += new_devices
228
+ addresses += new_addresses
229
+ payments += new_payments
230
+ orders += ring11_orders + b01_orders
231
+
232
+ print(f" Added: {len(new_customers)} customers, {len(new_devices)} devices, "
233
+ f"{len(new_addresses)} addresses, {len(new_payments)} payments, "
234
+ f"{len(ring11_orders + b01_orders)} orders")
235
+
236
+ _save("customers.json", customers)
237
+ _save("devices.json", devices)
238
+ _save("addresses.json", addresses)
239
+ _save("payment_instruments.json", payments)
240
+ _save("orders.json", orders)
241
+ _save("ground_truth.json", gt)
242
+
243
+ # =========================================================================
244
+ # Summary verification
245
+ # =========================================================================
246
+ r11_total = sum(o["amount"] for o in ring11_orders)
247
+ b01_total = sum(o["amount"] for o in b01_orders)
248
+ r11_dev_counts = {}
249
+ for o in ring11_orders:
250
+ k = (o["customer_id"], o["device_id"])
251
+ r11_dev_counts[k] = r11_dev_counts.get(k, 0) + 1
252
+ b01_shared_dev_counts = {}
253
+ for o in b01_orders:
254
+ if o["device_id"] == B01_SHARED_DEV:
255
+ b01_shared_dev_counts[o["customer_id"]] = b01_shared_dev_counts.get(o["customer_id"], 0) + 1
256
+
257
+ print(f"\n--- Verification ---")
258
+ print(f"Totals after augment: {len(customers)} customers, {len(devices)} devices, "
259
+ f"{len(addresses)} addresses, {len(payments)} payments, {len(orders)} orders")
260
+ print(f"RING11 total_amount = {r11_total} (need <= 5000 for auto_hold)")
261
+ print(f"RING11 orders per (customer, shared_device):")
262
+ for k, v in sorted(r11_dev_counts.items()):
263
+ print(f" {k[0]}@{k[1]}: {v} orders (need >= 2)")
264
+ print(f"BOUNDARY01 total_amount = {b01_total}")
265
+ print(f"BOUNDARY01 shared_device orders per customer (need <= 1):")
266
+ for cid, cnt in sorted(b01_shared_dev_counts.items()):
267
+ print(f" {cid}: {cnt}")
268
+ print(f"ground_truth.json rings: {len(gt['rings'])} (RING11 appended, BOUNDARY01 excluded)")
269
+
270
+
271
+ if __name__ == "__main__":
272
+ augment()
data/generate_synthetic_data.py ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ generate_synthetic_data.py
3
+ --------------------------
4
+ Generates synthetic order/account data for RingGuard.
5
+ Writes six JSON files to data/synthetic/ (Phase 1 defaults) or to a
6
+ specified output directory (Phase 2 training dataset).
7
+
8
+ Usage:
9
+ # Phase 1 (default β€” 500 customers, 10 rings, 5 stress clusters):
10
+ python data/generate_synthetic_data.py
11
+
12
+ # Phase 2 training dataset (3000 customers, 60 rings, 60 benign clusters):
13
+ python data/generate_synthetic_data.py --train
14
+
15
+ # Custom scale:
16
+ python data/generate_synthetic_data.py --customers N --rings R --benign B
17
+
18
+ Seed: 42 (fixed for reproducibility β€” every run produces byte-identical output).
19
+
20
+ Design:
21
+ - Fraud rings (strong-link abuse): share device, payment instrument, or
22
+ contact details. Device/instrument pools are dedicated so rings can't
23
+ accidentally contaminate benign clusters.
24
+ - Benign/stress clusters (weak-link only): share exactly one address, with
25
+ dedicated per-member devices and payment instruments drawn from a
26
+ non-ring pool, ensuring ZERO strong-link overlap between cluster members.
27
+ - Remaining customers: ordinary unconnected activity.
28
+ """
29
+
30
+ import argparse
31
+ import hashlib
32
+ import json
33
+ import os
34
+ import random
35
+ from datetime import datetime, timedelta
36
+
37
+ from faker import Faker
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Configuration (Phase 1 defaults β€” overridable via CLI)
41
+ # ---------------------------------------------------------------------------
42
+ SEED = 42
43
+
44
+ BASE_DATE = datetime(2026, 1, 1)
45
+ END_DATE = datetime(2026, 6, 30)
46
+
47
+ fake = Faker("en_IN")
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Helpers
52
+ # ---------------------------------------------------------------------------
53
+ def _sha256(value: str) -> str:
54
+ return hashlib.sha256(value.encode()).hexdigest()
55
+
56
+
57
+ def _rand_date() -> str:
58
+ delta = END_DATE - BASE_DATE
59
+ return (BASE_DATE + timedelta(days=random.randint(0, delta.days))).strftime("%Y-%m-%d")
60
+
61
+
62
+ def _rand_timestamp() -> str:
63
+ delta = END_DATE - BASE_DATE
64
+ dt = BASE_DATE + timedelta(
65
+ days=random.randint(0, delta.days),
66
+ hours=random.randint(0, 23),
67
+ minutes=random.randint(0, 59),
68
+ seconds=random.randint(0, 59),
69
+ )
70
+ return dt.strftime("%Y-%m-%dT%H:%M:%S")
71
+
72
+
73
+ def _amount(low=200, high=5000) -> int:
74
+ return random.randint(low, high)
75
+
76
+
77
+ def _make_order(
78
+ order_id: int,
79
+ customer_id: str,
80
+ device_id: str,
81
+ instrument_id: str,
82
+ address_id: str,
83
+ status: str,
84
+ coupon_id=None,
85
+ amount=None,
86
+ ) -> dict:
87
+ return {
88
+ "order_id": f"ORD{order_id:05d}",
89
+ "customer_id": customer_id,
90
+ "device_id": device_id,
91
+ "payment_instrument_id": instrument_id,
92
+ "address_id": address_id,
93
+ "amount": amount if amount is not None else _amount(),
94
+ "status": status,
95
+ "coupon_id": coupon_id,
96
+ "timestamp": _rand_timestamp(),
97
+ }
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # 1. Customers
102
+ # ---------------------------------------------------------------------------
103
+ def build_customers(n: int) -> list[dict]:
104
+ customers = []
105
+ for i in range(1, n + 1):
106
+ email = fake.email()
107
+ phone = fake.phone_number()
108
+ customers.append(
109
+ {
110
+ "customer_id": f"CUST{i:04d}",
111
+ "email_hash": _sha256(email),
112
+ "phone_hash": _sha256(phone),
113
+ "signup_date": _rand_date(),
114
+ }
115
+ )
116
+ return customers
117
+
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # 2. Devices
121
+ # ---------------------------------------------------------------------------
122
+ def build_devices(n: int) -> list[dict]:
123
+ devices = []
124
+ for i in range(1, n + 1):
125
+ fp = fake.uuid4()
126
+ devices.append(
127
+ {
128
+ "device_id": f"DEV{i:04d}",
129
+ "fingerprint_hash": _sha256(fp),
130
+ }
131
+ )
132
+ return devices
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # 3. Addresses
137
+ # ---------------------------------------------------------------------------
138
+ def build_addresses(n: int) -> list[dict]:
139
+ addresses = []
140
+ for i in range(1, n + 1):
141
+ raw = f"{fake.building_number()} {fake.street_name()}, {fake.city()}, {fake.state()}"
142
+ addresses.append(
143
+ {
144
+ "address_id": f"ADDR{i:04d}",
145
+ "normalized_address": raw.lower().strip(),
146
+ }
147
+ )
148
+ return addresses
149
+
150
+
151
+ # ---------------------------------------------------------------------------
152
+ # 4. Payment instruments
153
+ # ---------------------------------------------------------------------------
154
+ def build_payment_instruments(n: int) -> list[dict]:
155
+ instruments = []
156
+ for i in range(1, n + 1):
157
+ ptype = random.choice(["card", "upi"])
158
+ if ptype == "card":
159
+ last4 = str(random.randint(1000, 9999))
160
+ else:
161
+ last4 = fake.email().replace("@", "_")
162
+ instruments.append(
163
+ {
164
+ "instrument_id": f"PAY{i:04d}",
165
+ "type": ptype,
166
+ "last4_hash": _sha256(last4),
167
+ }
168
+ )
169
+ return instruments
170
+
171
+
172
+ # ---------------------------------------------------------------------------
173
+ # 5. Seed fraud rings
174
+ # ---------------------------------------------------------------------------
175
+ def seed_rings(
176
+ customers: list[dict],
177
+ devices: list[dict],
178
+ payment_instruments: list[dict],
179
+ addresses: list[dict],
180
+ order_counter: list[int],
181
+ num_rings: int = 10,
182
+ ring_pool_size: int = 60,
183
+ ) -> tuple[list[dict], list[dict], list[str], set, set]:
184
+ """
185
+ Uses the first ring_pool_size devices/instruments as the ring-exclusive pool
186
+ so ring resources never leak into benign clusters.
187
+
188
+ Each ring uses a dedicated shared_device/shared_instrument that ALL its members
189
+ place 2+ orders with, creating strong intra-ring edges detectable by the
190
+ count >= 2 threshold in detect_communities.py.
191
+
192
+ Returns ring_orders, ring_defs, ring_member_ids,
193
+ used_device_ids, used_instrument_ids
194
+ """
195
+ ring_orders = []
196
+ ring_defs = []
197
+ ring_member_ids: list[str] = []
198
+
199
+ # Dedicated pools for rings β€” first ring_pool_size devices/instruments
200
+ ring_dev_pool = [d["device_id"] for d in devices[:ring_pool_size]]
201
+ ring_instr_pool = [p["instrument_id"] for p in payment_instruments[:ring_pool_size]]
202
+
203
+ cust_pool = [c["customer_id"] for c in customers]
204
+ cust_cursor = 0
205
+
206
+ used_device_ids: set = set()
207
+ used_instrument_ids: set = set()
208
+
209
+ # Build customer lookup for fast access
210
+ cust_by_id = {c["customer_id"]: c for c in customers}
211
+
212
+ for ring_num in range(1, num_rings + 1):
213
+ ring_size = random.randint(3, 6)
214
+ members = cust_pool[cust_cursor: cust_cursor + ring_size]
215
+ cust_cursor += ring_size
216
+ ring_member_ids.extend(members)
217
+
218
+ ring_id = f"RING{ring_num:02d}"
219
+
220
+ # Pick strong-link type randomly per ring
221
+ strong_link_type = random.choice(["device", "payment", "contact"])
222
+
223
+ # Shared resources drawn from ring-exclusive pool
224
+ shared_device = random.choice(ring_dev_pool)
225
+ shared_instrument = random.choice(ring_instr_pool)
226
+ used_device_ids.add(shared_device)
227
+ used_instrument_ids.add(shared_instrument)
228
+
229
+ coupon_id = f"COUP{ring_num:03d}" if random.random() > 0.3 else None
230
+
231
+ if strong_link_type == "contact":
232
+ first_email_hash = cust_by_id[members[0]]["email_hash"]
233
+ first_phone_hash = cust_by_id[members[0]]["phone_hash"]
234
+ for mid in members[1:]:
235
+ if random.random() > 0.5:
236
+ cust_by_id[mid]["email_hash"] = first_email_hash
237
+ else:
238
+ cust_by_id[mid]["phone_hash"] = first_phone_hash
239
+
240
+ for member_id in members:
241
+ n_orders = random.randint(2, 4)
242
+ for j in range(n_orders):
243
+ order_counter[0] += 1
244
+ amount = _amount(3000, 10000) if j == 0 else _amount(500, 5000)
245
+ if j == 0:
246
+ status = "success"
247
+ elif j == 1:
248
+ status = random.choice(["returned", "chargeback"])
249
+ else:
250
+ status = random.choice(["success", "returned"])
251
+
252
+ # For device/payment rings: all orders use the shared resource
253
+ # (ensures count >= 2 for all members β†’ detected as strong edge).
254
+ # For contact rings: use any ring-pool device/instrument.
255
+ dev_id = shared_device if strong_link_type == "device" \
256
+ else random.choice(ring_dev_pool)
257
+ instr_id = shared_instrument if strong_link_type == "payment" \
258
+ else random.choice(ring_instr_pool)
259
+ used_device_ids.add(dev_id)
260
+ used_instrument_ids.add(instr_id)
261
+
262
+ addr_id = random.choice(addresses)["address_id"]
263
+ cup = coupon_id if (coupon_id and j > 0) else None
264
+
265
+ ring_orders.append(
266
+ _make_order(
267
+ order_counter[0],
268
+ member_id,
269
+ dev_id,
270
+ instr_id,
271
+ addr_id,
272
+ status,
273
+ coupon_id=cup,
274
+ amount=amount,
275
+ )
276
+ )
277
+
278
+ ring_defs.append(
279
+ {
280
+ "ring_id": ring_id,
281
+ "customer_ids": members,
282
+ "type": "fraud",
283
+ }
284
+ )
285
+
286
+ return ring_orders, ring_defs, ring_member_ids, used_device_ids, used_instrument_ids
287
+
288
+
289
+ # ---------------------------------------------------------------------------
290
+ # 6. Seed benign clusters (weak-link only: shared address)
291
+ # ---------------------------------------------------------------------------
292
+ def seed_benign_clusters(
293
+ customers: list[dict],
294
+ devices: list[dict],
295
+ payment_instruments: list[dict],
296
+ addresses: list[dict],
297
+ order_counter: list[int],
298
+ used_customer_ids: list[str],
299
+ excluded_device_ids: set,
300
+ excluded_instrument_ids: set,
301
+ num_benign_clusters: int = 5,
302
+ benign_start_addr_idx: int = 10,
303
+ ) -> tuple[list[dict], list[dict], list[str], set, set, set]:
304
+ """
305
+ Each benign-cluster member gets a UNIQUE device and payment instrument
306
+ drawn from a pool that EXCLUDES all ring-used resources.
307
+ Each benign cluster uses a dedicated address not used by ordinary customers.
308
+ Returns the consumed device/instrument/address IDs so fill_ordinary_orders
309
+ can exclude them entirely, preventing any accidental entity-sharing.
310
+ """
311
+ benign_orders = []
312
+ benign_defs = []
313
+ benign_member_ids: list[str] = []
314
+ benign_used_device_ids: set = set()
315
+ benign_used_instrument_ids: set = set()
316
+ benign_used_address_ids: set = set()
317
+
318
+ # Benign-safe pools: devices/instruments not used by rings
319
+ safe_devices = [
320
+ d["device_id"] for d in devices
321
+ if d["device_id"] not in excluded_device_ids
322
+ ]
323
+ safe_instruments = [
324
+ p["instrument_id"] for p in payment_instruments
325
+ if p["instrument_id"] not in excluded_instrument_ids
326
+ ]
327
+
328
+ # Shuffle safe pools (deterministically seeded)
329
+ random.shuffle(safe_devices)
330
+ random.shuffle(safe_instruments)
331
+
332
+ dev_cursor = 0
333
+ instr_cursor = 0
334
+
335
+ available = [c["customer_id"] for c in customers if c["customer_id"] not in used_customer_ids]
336
+ cust_cursor = 0
337
+
338
+ for sc_num in range(1, num_benign_clusters + 1):
339
+ cluster_size = random.randint(2, 4)
340
+ members = available[cust_cursor: cust_cursor + cluster_size]
341
+ cust_cursor += cluster_size
342
+ benign_member_ids.extend(members)
343
+
344
+ # ONE shared address (weak link only) β€” dedicated slot, not reused elsewhere
345
+ shared_address = addresses[benign_start_addr_idx + sc_num]["address_id"]
346
+ benign_used_address_ids.add(shared_address)
347
+ cluster_id = f"BENIGN{sc_num:02d}"
348
+
349
+ for member_id in members:
350
+ n_orders = random.randint(1, 3)
351
+ # Assign a unique device and instrument PER MEMBER (not per order)
352
+ # so no two members in this cluster share a device or instrument
353
+ member_dev = safe_devices[dev_cursor]
354
+ member_instr = safe_instruments[instr_cursor]
355
+ benign_used_device_ids.add(member_dev)
356
+ benign_used_instrument_ids.add(member_instr)
357
+ dev_cursor += 1
358
+ instr_cursor += 1
359
+
360
+ for _ in range(n_orders):
361
+ order_counter[0] += 1
362
+ status = random.choice(["success", "success", "returned"])
363
+ benign_orders.append(
364
+ _make_order(
365
+ order_counter[0],
366
+ member_id,
367
+ member_dev,
368
+ member_instr,
369
+ shared_address,
370
+ status,
371
+ )
372
+ )
373
+
374
+ benign_defs.append(
375
+ {
376
+ "cluster_id": cluster_id,
377
+ "customer_ids": members,
378
+ "type": "legitimate_shared_address",
379
+ }
380
+ )
381
+
382
+ return (benign_orders, benign_defs, benign_member_ids,
383
+ benign_used_device_ids, benign_used_instrument_ids, benign_used_address_ids)
384
+
385
+
386
+ # ---------------------------------------------------------------------------
387
+ # 7. Fill remaining ordinary orders
388
+ # ---------------------------------------------------------------------------
389
+ def fill_ordinary_orders(
390
+ customers: list[dict],
391
+ devices: list[dict],
392
+ payment_instruments: list[dict],
393
+ addresses: list[dict],
394
+ order_counter: list[int],
395
+ current_order_count: int,
396
+ target_orders: int,
397
+ excluded_customer_ids: set = None,
398
+ excluded_device_ids: set = None,
399
+ excluded_instrument_ids: set = None,
400
+ excluded_address_ids: set = None,
401
+ ) -> list[dict]:
402
+ # Build pools excluding reserved ring/benign resources
403
+ ordinary_customers = [
404
+ c for c in customers
405
+ if excluded_customer_ids is None or c["customer_id"] not in excluded_customer_ids
406
+ ]
407
+ ordinary_devices = [
408
+ d for d in devices
409
+ if excluded_device_ids is None or d["device_id"] not in excluded_device_ids
410
+ ]
411
+ ordinary_instruments = [
412
+ p for p in payment_instruments
413
+ if excluded_instrument_ids is None or p["instrument_id"] not in excluded_instrument_ids
414
+ ]
415
+ ordinary_addresses = [
416
+ a for a in addresses
417
+ if excluded_address_ids is None or a["address_id"] not in excluded_address_ids
418
+ ]
419
+ orders = []
420
+ needed = max(0, target_orders - current_order_count)
421
+ for _ in range(needed):
422
+ order_counter[0] += 1
423
+ cust = random.choice(ordinary_customers)
424
+ dev = random.choice(ordinary_devices)
425
+ instr = random.choice(ordinary_instruments)
426
+ addr = random.choice(ordinary_addresses)
427
+ status = random.choices(
428
+ ["success", "returned", "chargeback"], weights=[80, 15, 5], k=1
429
+ )[0]
430
+ orders.append(
431
+ _make_order(
432
+ order_counter[0],
433
+ cust["customer_id"],
434
+ dev["device_id"],
435
+ instr["instrument_id"],
436
+ addr["address_id"],
437
+ status,
438
+ )
439
+ )
440
+ return orders
441
+
442
+
443
+ # ---------------------------------------------------------------------------
444
+ # Core generation logic (shared between Phase 1 and Phase 2 scale)
445
+ # ---------------------------------------------------------------------------
446
+
447
+ def generate(
448
+ num_customers: int,
449
+ num_rings: int,
450
+ num_benign_clusters: int,
451
+ target_orders: int,
452
+ out_dir: str,
453
+ is_phase1_compat: bool = False,
454
+ ) -> None:
455
+ """
456
+ Generate and write a complete synthetic dataset.
457
+
458
+ Phase 1 compatibility mode (is_phase1_compat=True):
459
+ - ground_truth key: "stress_clusters" (legacy name for Phase 1 data)
460
+ - benign cluster IDs: STRESS01, STRESS02, ...
461
+ Phase 2 mode (is_phase1_compat=False):
462
+ - ground_truth key: "benign_clusters"
463
+ - benign cluster IDs: BENIGN01, BENIGN02, ...
464
+ """
465
+ os.makedirs(out_dir, exist_ok=True)
466
+
467
+ # Scale: same ratios as Phase 1 (500β†’400 devices, 350 addresses, 380 instruments)
468
+ # Phase 1 ratios: devices=0.80, addresses=0.70, instruments=0.76
469
+ num_devices = round(num_customers * 0.80)
470
+ num_addresses = round(num_customers * 0.70)
471
+ num_instruments = round(num_customers * 0.76)
472
+
473
+ print(f"Building synthetic data (seed={SEED})...")
474
+ print(f" Scale: {num_customers} customers, {num_rings} rings, "
475
+ f"{num_benign_clusters} benign clusters, ~{target_orders} orders")
476
+ print(f" Pool sizes: {num_devices} devices, {num_addresses} addresses, "
477
+ f"{num_instruments} instruments")
478
+
479
+ customers = build_customers(num_customers)
480
+ devices = build_devices(num_devices)
481
+ addresses = build_addresses(num_addresses)
482
+ payment_instruments = build_payment_instruments(num_instruments)
483
+
484
+ order_counter = [0]
485
+
486
+ # -----------------------------------------------------------------------
487
+ # Pool partitioning strategy (avoids accidental cross-contamination):
488
+ # Ring pool size: enough for all rings Γ— 1 shared device each, with buffer.
489
+ # Benign pool size: enough for all benign clusters Γ— max-size (4) members.
490
+ # Both pools sized to prevent exhaustion.
491
+ # -----------------------------------------------------------------------
492
+ RING_POOL_SIZE = max(60, num_rings * 6)
493
+ BENIGN_POOL_SIZE = max(60, num_benign_clusters * 4 * 2) # 2 = device + instrument per member
494
+
495
+ if RING_POOL_SIZE + BENIGN_POOL_SIZE > num_devices:
496
+ raise ValueError(
497
+ f"Device pool ({num_devices}) too small for ring pool ({RING_POOL_SIZE}) "
498
+ f"+ benign pool ({BENIGN_POOL_SIZE}). Increase num_customers."
499
+ )
500
+
501
+ ring_dev_reserved = set(d["device_id"] for d in devices[:RING_POOL_SIZE])
502
+ ring_instr_reserved = set(p["instrument_id"] for p in payment_instruments[:RING_POOL_SIZE])
503
+ benign_dev_reserved = set(d["device_id"] for d in devices[RING_POOL_SIZE:RING_POOL_SIZE + BENIGN_POOL_SIZE])
504
+ benign_instr_reserved = set(p["instrument_id"] for p in payment_instruments[RING_POOL_SIZE:RING_POOL_SIZE + BENIGN_POOL_SIZE])
505
+
506
+ ring_orders, ring_defs, ring_member_ids, used_devs, used_instrs = seed_rings(
507
+ customers, devices, payment_instruments, addresses, order_counter,
508
+ num_rings=num_rings,
509
+ ring_pool_size=RING_POOL_SIZE,
510
+ )
511
+
512
+ # Benign pool = benign-reserved zone only (completely disjoint from ring zone)
513
+ benign_only_devs = benign_dev_reserved
514
+ benign_only_instrs = benign_instr_reserved
515
+
516
+ (benign_orders, benign_defs, benign_member_ids,
517
+ benign_devs, benign_instrs, benign_addrs) = seed_benign_clusters(
518
+ customers,
519
+ devices,
520
+ payment_instruments,
521
+ addresses,
522
+ order_counter,
523
+ used_customer_ids=ring_member_ids,
524
+ excluded_device_ids=set(d["device_id"] for d in devices) - benign_only_devs,
525
+ excluded_instrument_ids=set(p["instrument_id"] for p in payment_instruments) - benign_only_instrs,
526
+ num_benign_clusters=num_benign_clusters,
527
+ benign_start_addr_idx=10,
528
+ )
529
+
530
+ # Ordinary orders use everything EXCEPT ring zone, benign zone, and benign addresses
531
+ all_excluded_devs = ring_dev_reserved | benign_dev_reserved
532
+ all_excluded_instrs = ring_instr_reserved | benign_instr_reserved
533
+ all_excluded_custs = set(ring_member_ids) | set(benign_member_ids)
534
+
535
+ ordinary_orders = fill_ordinary_orders(
536
+ customers,
537
+ devices,
538
+ payment_instruments,
539
+ addresses,
540
+ order_counter,
541
+ current_order_count=len(ring_orders) + len(benign_orders),
542
+ target_orders=target_orders,
543
+ excluded_customer_ids=all_excluded_custs,
544
+ excluded_device_ids=all_excluded_devs,
545
+ excluded_instrument_ids=all_excluded_instrs,
546
+ excluded_address_ids=benign_addrs,
547
+ )
548
+
549
+ all_orders = ring_orders + benign_orders + ordinary_orders
550
+ all_orders.sort(key=lambda o: o["order_id"])
551
+
552
+ # Ground truth β€” Phase 1 compat uses "stress_clusters" key, Phase 2 uses "benign_clusters"
553
+ if is_phase1_compat:
554
+ # Rename BENIGN β†’ STRESS for backward compat
555
+ stress_defs = []
556
+ for i, d in enumerate(benign_defs, start=1):
557
+ stress_defs.append({
558
+ "cluster_id": f"STRESS{i:02d}",
559
+ "customer_ids": d["customer_ids"],
560
+ "type": d["type"],
561
+ })
562
+ ground_truth = {
563
+ "rings": ring_defs,
564
+ "stress_clusters": stress_defs,
565
+ }
566
+ else:
567
+ ground_truth = {
568
+ "rings": ring_defs,
569
+ "benign_clusters": benign_defs,
570
+ }
571
+
572
+ def _write(filename, data):
573
+ path = os.path.join(out_dir, filename)
574
+ with open(path, "w", encoding="utf-8") as f:
575
+ json.dump(data, f, indent=2, ensure_ascii=False)
576
+ n = len(data) if isinstance(data, list) else "dict"
577
+ print(f" Wrote {path} ({n})")
578
+
579
+ _write("customers.json", customers)
580
+ _write("devices.json", devices)
581
+ _write("addresses.json", addresses)
582
+ _write("payment_instruments.json", payment_instruments)
583
+ _write("orders.json", all_orders)
584
+
585
+ path = os.path.join(out_dir, "ground_truth.json")
586
+ with open(path, "w", encoding="utf-8") as f:
587
+ json.dump(ground_truth, f, indent=2, ensure_ascii=False)
588
+ print(f" Wrote {path} (dict)")
589
+
590
+ # Console summary
591
+ status_counts = {}
592
+ for o in all_orders:
593
+ status_counts[o["status"]] = status_counts.get(o["status"], 0) + 1
594
+
595
+ ring_set = set(ring_member_ids)
596
+ benign_set = set(benign_member_ids)
597
+ ordinary_count = sum(
598
+ 1 for c in customers
599
+ if c["customer_id"] not in ring_set and c["customer_id"] not in benign_set
600
+ )
601
+ benign_label = "stress_clusters" if is_phase1_compat else "benign_clusters"
602
+
603
+ print("\n========== Synthetic Data Summary ==========")
604
+ print(f" Customers total : {len(customers)}")
605
+ print(f" Ring members : {len(ring_set)}")
606
+ print(f" Benign members : {len(benign_set)}")
607
+ print(f" Ordinary customers : {ordinary_count}")
608
+ print(f" Devices : {len(devices)}")
609
+ print(f" Addresses : {len(addresses)}")
610
+ print(f" Payment instruments : {len(payment_instruments)}")
611
+ print(f" Orders total : {len(all_orders)}")
612
+ print(f" success : {status_counts.get('success', 0)}")
613
+ print(f" returned : {status_counts.get('returned', 0)}")
614
+ print(f" chargeback : {status_counts.get('chargeback', 0)}")
615
+ print(f" Fraud rings seeded : {len(ring_defs)}")
616
+ print(f" {benign_label:22s}: {len(benign_defs)}")
617
+ print("============================================")
618
+
619
+
620
+ # ---------------------------------------------------------------------------
621
+ # Main β€” supports Phase 1 default and Phase 2 training scale
622
+ # ---------------------------------------------------------------------------
623
+ def main():
624
+ parser = argparse.ArgumentParser()
625
+ parser.add_argument("--train", action="store_true",
626
+ help="Generate Phase 2 training dataset to data/synthetic_train/")
627
+ parser.add_argument("--customers", type=int, default=None,
628
+ help="Override number of customers")
629
+ parser.add_argument("--rings", type=int, default=None,
630
+ help="Override number of fraud rings")
631
+ parser.add_argument("--benign", type=int, default=None,
632
+ help="Override number of benign clusters")
633
+ parser.add_argument("--out-dir", default=None,
634
+ help="Override output directory")
635
+ args = parser.parse_args()
636
+
637
+ # Re-seed deterministically (ensures same output regardless of import order)
638
+ random.seed(SEED)
639
+ fake.seed_instance(SEED)
640
+
641
+ if args.train:
642
+ # Phase 2 training scale: 3000 customers, 60 rings, 60 benign clusters
643
+ num_customers = args.customers or 3000
644
+ num_rings = args.rings or 60
645
+ num_benign = args.benign or 60
646
+ # Phase 1 ratios applied at 3000 customers β†’ ~7200 orders
647
+ target_orders = round(num_customers * 1200 / 500)
648
+ out_dir = args.out_dir or os.path.join(os.path.dirname(__file__), "synthetic_train")
649
+ generate(num_customers, num_rings, num_benign, target_orders, out_dir, is_phase1_compat=False)
650
+ else:
651
+ # Phase 1 defaults (preserves backward compat)
652
+ num_customers = args.customers or 500
653
+ num_rings = args.rings or 10
654
+ num_benign = args.benign or 5
655
+ target_orders = 1200
656
+ out_dir = args.out_dir or os.path.join(os.path.dirname(__file__), "synthetic")
657
+ generate(num_customers, num_rings, num_benign, target_orders, out_dir, is_phase1_compat=True)
658
+
659
+
660
+ if __name__ == "__main__":
661
+ main()
data/synthetic/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
data/synthetic/addresses.json ADDED
@@ -0,0 +1,1430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "address_id": "ADDR0001",
4
+ "normalized_address": "89/354 dey road, saharsa, telangana"
5
+ },
6
+ {
7
+ "address_id": "ADDR0002",
8
+ "normalized_address": "158 hans, malda, bihar"
9
+ },
10
+ {
11
+ "address_id": "ADDR0003",
12
+ "normalized_address": "661 bhatia zila, bikaner, meghalaya"
13
+ },
14
+ {
15
+ "address_id": "ADDR0004",
16
+ "normalized_address": "715 rau circle, bilaspur, odisha"
17
+ },
18
+ {
19
+ "address_id": "ADDR0005",
20
+ "normalized_address": "67 saha chowk, asansol, mizoram"
21
+ },
22
+ {
23
+ "address_id": "ADDR0006",
24
+ "normalized_address": "772 wadhwa circle, rajpur sonarpur, manipur"
25
+ },
26
+ {
27
+ "address_id": "ADDR0007",
28
+ "normalized_address": "02 pingle circle, madanapalle, west bengal"
29
+ },
30
+ {
31
+ "address_id": "ADDR0008",
32
+ "normalized_address": "11 mann path, nagaon, chhattisgarh"
33
+ },
34
+ {
35
+ "address_id": "ADDR0009",
36
+ "normalized_address": "h.no. 545 menon road, bhopal, uttar pradesh"
37
+ },
38
+ {
39
+ "address_id": "ADDR0010",
40
+ "normalized_address": "296 mody nagar, thrissur, goa"
41
+ },
42
+ {
43
+ "address_id": "ADDR0011",
44
+ "normalized_address": "70/185 basu zila, ongole, odisha"
45
+ },
46
+ {
47
+ "address_id": "ADDR0012",
48
+ "normalized_address": "h.no. 97 purohit path, medininagar, chhattisgarh"
49
+ },
50
+ {
51
+ "address_id": "ADDR0013",
52
+ "normalized_address": "877 dixit chowk, hubli–dharwad, uttar pradesh"
53
+ },
54
+ {
55
+ "address_id": "ADDR0014",
56
+ "normalized_address": "96/63 nori marg, mango, telangana"
57
+ },
58
+ {
59
+ "address_id": "ADDR0015",
60
+ "normalized_address": "h.no. 12 more nagar, karaikudi, assam"
61
+ },
62
+ {
63
+ "address_id": "ADDR0016",
64
+ "normalized_address": "989 vig nagar, fatehpur, mizoram"
65
+ },
66
+ {
67
+ "address_id": "ADDR0017",
68
+ "normalized_address": "h.no. 182 maharaj nagar, sambalpur, uttarakhand"
69
+ },
70
+ {
71
+ "address_id": "ADDR0018",
72
+ "normalized_address": "97/189 ratti path, pallavaram, west bengal"
73
+ },
74
+ {
75
+ "address_id": "ADDR0019",
76
+ "normalized_address": "h.no. 562 bajwa marg, eluru, himachal pradesh"
77
+ },
78
+ {
79
+ "address_id": "ADDR0020",
80
+ "normalized_address": "h.no. 118 mangal street, south dumdum, himachal pradesh"
81
+ },
82
+ {
83
+ "address_id": "ADDR0021",
84
+ "normalized_address": "h.no. 246 bhargava street, bokaro, andhra pradesh"
85
+ },
86
+ {
87
+ "address_id": "ADDR0022",
88
+ "normalized_address": "04/615 sachar street, rampur, madhya pradesh"
89
+ },
90
+ {
91
+ "address_id": "ADDR0023",
92
+ "normalized_address": "h.no. 033 khalsa, danapur, tripura"
93
+ },
94
+ {
95
+ "address_id": "ADDR0024",
96
+ "normalized_address": "61/40 shetty path, amritsar, meghalaya"
97
+ },
98
+ {
99
+ "address_id": "ADDR0025",
100
+ "normalized_address": "h.no. 630 chaudhuri road, miryalaguda, rajasthan"
101
+ },
102
+ {
103
+ "address_id": "ADDR0026",
104
+ "normalized_address": "496 chacko chowk, ajmer, kerala"
105
+ },
106
+ {
107
+ "address_id": "ADDR0027",
108
+ "normalized_address": "23/86 seth circle, mumbai, uttarakhand"
109
+ },
110
+ {
111
+ "address_id": "ADDR0028",
112
+ "normalized_address": "94 acharya street, ongole, himachal pradesh"
113
+ },
114
+ {
115
+ "address_id": "ADDR0029",
116
+ "normalized_address": "46/53 uppal, ranchi, goa"
117
+ },
118
+ {
119
+ "address_id": "ADDR0030",
120
+ "normalized_address": "89/141 das marg, miryalaguda, himachal pradesh"
121
+ },
122
+ {
123
+ "address_id": "ADDR0031",
124
+ "normalized_address": "39 nadkarni circle, bhilwara, tripura"
125
+ },
126
+ {
127
+ "address_id": "ADDR0032",
128
+ "normalized_address": "74/355 sharaf ganj, pune, tamil nadu"
129
+ },
130
+ {
131
+ "address_id": "ADDR0033",
132
+ "normalized_address": "097 andra nagar, haridwar, nagaland"
133
+ },
134
+ {
135
+ "address_id": "ADDR0034",
136
+ "normalized_address": "76/811 balasubramanian circle, amravati, kerala"
137
+ },
138
+ {
139
+ "address_id": "ADDR0035",
140
+ "normalized_address": "95 goel marg, amravati, manipur"
141
+ },
142
+ {
143
+ "address_id": "ADDR0036",
144
+ "normalized_address": "h.no. 37 kant nagar, parbhani, sikkim"
145
+ },
146
+ {
147
+ "address_id": "ADDR0037",
148
+ "normalized_address": "53/194 kaur ganj, mirzapur, arunachal pradesh"
149
+ },
150
+ {
151
+ "address_id": "ADDR0038",
152
+ "normalized_address": "h.no. 79 amble ganj, tenali, bihar"
153
+ },
154
+ {
155
+ "address_id": "ADDR0039",
156
+ "normalized_address": "h.no. 98 garde chowk, buxar, sikkim"
157
+ },
158
+ {
159
+ "address_id": "ADDR0040",
160
+ "normalized_address": "99/219 bhattacharyya street, rampur, odisha"
161
+ },
162
+ {
163
+ "address_id": "ADDR0041",
164
+ "normalized_address": "843 walia nagar, karaikudi, manipur"
165
+ },
166
+ {
167
+ "address_id": "ADDR0042",
168
+ "normalized_address": "h.no. 605 ray marg, bhilai, sikkim"
169
+ },
170
+ {
171
+ "address_id": "ADDR0043",
172
+ "normalized_address": "77/58 kapur nagar, alwar, maharashtra"
173
+ },
174
+ {
175
+ "address_id": "ADDR0044",
176
+ "normalized_address": "75/06 natt ganj, aurangabad, uttar pradesh"
177
+ },
178
+ {
179
+ "address_id": "ADDR0045",
180
+ "normalized_address": "h.no. 361 hegde ganj, ozhukarai, jharkhand"
181
+ },
182
+ {
183
+ "address_id": "ADDR0046",
184
+ "normalized_address": "h.no. 107 bajwa marg, chandrapur, haryana"
185
+ },
186
+ {
187
+ "address_id": "ADDR0047",
188
+ "normalized_address": "h.no. 735 nath nagar, srinagar, tripura"
189
+ },
190
+ {
191
+ "address_id": "ADDR0048",
192
+ "normalized_address": "97/17 dash, ghaziabad, arunachal pradesh"
193
+ },
194
+ {
195
+ "address_id": "ADDR0049",
196
+ "normalized_address": "51 bhatia street, pimpri-chinchwad, jharkhand"
197
+ },
198
+ {
199
+ "address_id": "ADDR0050",
200
+ "normalized_address": "61/10 ben circle, eluru, madhya pradesh"
201
+ },
202
+ {
203
+ "address_id": "ADDR0051",
204
+ "normalized_address": "h.no. 963 anne, yamunanagar, manipur"
205
+ },
206
+ {
207
+ "address_id": "ADDR0052",
208
+ "normalized_address": "99 nori zila, malda, mizoram"
209
+ },
210
+ {
211
+ "address_id": "ADDR0053",
212
+ "normalized_address": "h.no. 93 chauhan zila, hindupur, tamil nadu"
213
+ },
214
+ {
215
+ "address_id": "ADDR0054",
216
+ "normalized_address": "h.no. 939 upadhyay street, raichur, maharashtra"
217
+ },
218
+ {
219
+ "address_id": "ADDR0055",
220
+ "normalized_address": "97 kibe street, varanasi, arunachal pradesh"
221
+ },
222
+ {
223
+ "address_id": "ADDR0056",
224
+ "normalized_address": "496 parmar circle, ballia, nagaland"
225
+ },
226
+ {
227
+ "address_id": "ADDR0057",
228
+ "normalized_address": "274 oza, arrah, andhra pradesh"
229
+ },
230
+ {
231
+ "address_id": "ADDR0058",
232
+ "normalized_address": "92/03 bhagat circle, hapur, gujarat"
233
+ },
234
+ {
235
+ "address_id": "ADDR0059",
236
+ "normalized_address": "315 soni ganj, ghaziabad, tamil nadu"
237
+ },
238
+ {
239
+ "address_id": "ADDR0060",
240
+ "normalized_address": "h.no. 99 mistry path, ozhukarai, goa"
241
+ },
242
+ {
243
+ "address_id": "ADDR0061",
244
+ "normalized_address": "h.no. 707 aggarwal ganj, berhampur, manipur"
245
+ },
246
+ {
247
+ "address_id": "ADDR0062",
248
+ "normalized_address": "33/75 jha marg, udaipur, uttarakhand"
249
+ },
250
+ {
251
+ "address_id": "ADDR0063",
252
+ "normalized_address": "h.no. 501 balan path, ajmer, jharkhand"
253
+ },
254
+ {
255
+ "address_id": "ADDR0064",
256
+ "normalized_address": "39 bassi path, davanagere, gujarat"
257
+ },
258
+ {
259
+ "address_id": "ADDR0065",
260
+ "normalized_address": "h.no. 23 ghosh road, begusarai, haryana"
261
+ },
262
+ {
263
+ "address_id": "ADDR0066",
264
+ "normalized_address": "302 chaudhuri road, durgapur, madhya pradesh"
265
+ },
266
+ {
267
+ "address_id": "ADDR0067",
268
+ "normalized_address": "15/347 pingle ganj, tirunelveli, mizoram"
269
+ },
270
+ {
271
+ "address_id": "ADDR0068",
272
+ "normalized_address": "61/12 gupta street, katihar, madhya pradesh"
273
+ },
274
+ {
275
+ "address_id": "ADDR0069",
276
+ "normalized_address": "55/963 gulati nagar, jalgaon, karnataka"
277
+ },
278
+ {
279
+ "address_id": "ADDR0070",
280
+ "normalized_address": "042 suresh circle, jodhpur, uttar pradesh"
281
+ },
282
+ {
283
+ "address_id": "ADDR0071",
284
+ "normalized_address": "91 trivedi street, danapur, kerala"
285
+ },
286
+ {
287
+ "address_id": "ADDR0072",
288
+ "normalized_address": "87 nagy street, hajipur, tamil nadu"
289
+ },
290
+ {
291
+ "address_id": "ADDR0073",
292
+ "normalized_address": "77 rattan road, dhule, haryana"
293
+ },
294
+ {
295
+ "address_id": "ADDR0074",
296
+ "normalized_address": "736 mohan ganj, burhanpur, odisha"
297
+ },
298
+ {
299
+ "address_id": "ADDR0075",
300
+ "normalized_address": "548 desai zila, sagar, manipur"
301
+ },
302
+ {
303
+ "address_id": "ADDR0076",
304
+ "normalized_address": "53/90 vala chowk, purnia, manipur"
305
+ },
306
+ {
307
+ "address_id": "ADDR0077",
308
+ "normalized_address": "66 tank path, darbhanga, madhya pradesh"
309
+ },
310
+ {
311
+ "address_id": "ADDR0078",
312
+ "normalized_address": "h.no. 273 borra road, gwalior, jharkhand"
313
+ },
314
+ {
315
+ "address_id": "ADDR0079",
316
+ "normalized_address": "88/14 chaudhari ganj, madurai, kerala"
317
+ },
318
+ {
319
+ "address_id": "ADDR0080",
320
+ "normalized_address": "h.no. 835 narayan ganj, barasat, telangana"
321
+ },
322
+ {
323
+ "address_id": "ADDR0081",
324
+ "normalized_address": "43/928 anand road, ichalkaranji, uttarakhand"
325
+ },
326
+ {
327
+ "address_id": "ADDR0082",
328
+ "normalized_address": "h.no. 66 arora, anand, tripura"
329
+ },
330
+ {
331
+ "address_id": "ADDR0083",
332
+ "normalized_address": "633 som ganj, hazaribagh, odisha"
333
+ },
334
+ {
335
+ "address_id": "ADDR0084",
336
+ "normalized_address": "77/51 guha path, erode, gujarat"
337
+ },
338
+ {
339
+ "address_id": "ADDR0085",
340
+ "normalized_address": "78 prasad nagar, gangtok, west bengal"
341
+ },
342
+ {
343
+ "address_id": "ADDR0086",
344
+ "normalized_address": "407 nori path, gurgaon, maharashtra"
345
+ },
346
+ {
347
+ "address_id": "ADDR0087",
348
+ "normalized_address": "68/459 balay nagar, jamnagar, karnataka"
349
+ },
350
+ {
351
+ "address_id": "ADDR0088",
352
+ "normalized_address": "99/96 jayaraman marg, rajkot, nagaland"
353
+ },
354
+ {
355
+ "address_id": "ADDR0089",
356
+ "normalized_address": "37 narasimhan zila, avadi, assam"
357
+ },
358
+ {
359
+ "address_id": "ADDR0090",
360
+ "normalized_address": "182 suresh nagar, gaya, gujarat"
361
+ },
362
+ {
363
+ "address_id": "ADDR0091",
364
+ "normalized_address": "h.no. 643 manne ganj, malda, arunachal pradesh"
365
+ },
366
+ {
367
+ "address_id": "ADDR0092",
368
+ "normalized_address": "h.no. 80 narain street, katni, goa"
369
+ },
370
+ {
371
+ "address_id": "ADDR0093",
372
+ "normalized_address": "h.no. 286 biswas chowk, tiruppur, chhattisgarh"
373
+ },
374
+ {
375
+ "address_id": "ADDR0094",
376
+ "normalized_address": "467 mukherjee street, ambala, tamil nadu"
377
+ },
378
+ {
379
+ "address_id": "ADDR0095",
380
+ "normalized_address": "74/302 bhatt chowk, mahbubnagar, uttar pradesh"
381
+ },
382
+ {
383
+ "address_id": "ADDR0096",
384
+ "normalized_address": "859 sharma chowk, ramgarh, arunachal pradesh"
385
+ },
386
+ {
387
+ "address_id": "ADDR0097",
388
+ "normalized_address": "14/754 gill marg, kulti, goa"
389
+ },
390
+ {
391
+ "address_id": "ADDR0098",
392
+ "normalized_address": "67/62 chandran, varanasi, haryana"
393
+ },
394
+ {
395
+ "address_id": "ADDR0099",
396
+ "normalized_address": "484 kalla zila, bulandshahr, tripura"
397
+ },
398
+ {
399
+ "address_id": "ADDR0100",
400
+ "normalized_address": "15/11 nanda circle, hospet, goa"
401
+ },
402
+ {
403
+ "address_id": "ADDR0101",
404
+ "normalized_address": "h.no. 38 shere nagar, srinagar, rajasthan"
405
+ },
406
+ {
407
+ "address_id": "ADDR0102",
408
+ "normalized_address": "76/563 barad marg, chandrapur, bihar"
409
+ },
410
+ {
411
+ "address_id": "ADDR0103",
412
+ "normalized_address": "25/777 butala zila, bahraich, chhattisgarh"
413
+ },
414
+ {
415
+ "address_id": "ADDR0104",
416
+ "normalized_address": "42 badami street, belgaum, jharkhand"
417
+ },
418
+ {
419
+ "address_id": "ADDR0105",
420
+ "normalized_address": "h.no. 862 apte zila, jamnagar, manipur"
421
+ },
422
+ {
423
+ "address_id": "ADDR0106",
424
+ "normalized_address": "h.no. 23 walia nagar, mango, tamil nadu"
425
+ },
426
+ {
427
+ "address_id": "ADDR0107",
428
+ "normalized_address": "h.no. 816 sawhney road, khora , andhra pradesh"
429
+ },
430
+ {
431
+ "address_id": "ADDR0108",
432
+ "normalized_address": "h.no. 633 khare path, mysore, andhra pradesh"
433
+ },
434
+ {
435
+ "address_id": "ADDR0109",
436
+ "normalized_address": "00 handa path, muzaffarnagar, bihar"
437
+ },
438
+ {
439
+ "address_id": "ADDR0110",
440
+ "normalized_address": "22/068 narain zila, kishanganj, rajasthan"
441
+ },
442
+ {
443
+ "address_id": "ADDR0111",
444
+ "normalized_address": "938 sangha marg, ahmednagar, gujarat"
445
+ },
446
+ {
447
+ "address_id": "ADDR0112",
448
+ "normalized_address": "h.no. 552 ratti nagar, ahmedabad, madhya pradesh"
449
+ },
450
+ {
451
+ "address_id": "ADDR0113",
452
+ "normalized_address": "731 pillay road, jabalpur, gujarat"
453
+ },
454
+ {
455
+ "address_id": "ADDR0114",
456
+ "normalized_address": "49 chaudry chowk, chandrapur, bihar"
457
+ },
458
+ {
459
+ "address_id": "ADDR0115",
460
+ "normalized_address": "56/25 nadkarni street, jabalpur, karnataka"
461
+ },
462
+ {
463
+ "address_id": "ADDR0116",
464
+ "normalized_address": "h.no. 997 vaidya marg, ambala, odisha"
465
+ },
466
+ {
467
+ "address_id": "ADDR0117",
468
+ "normalized_address": "03/091 khalsa road, latur, punjab"
469
+ },
470
+ {
471
+ "address_id": "ADDR0118",
472
+ "normalized_address": "h.no. 025 minhas chowk, saharanpur, madhya pradesh"
473
+ },
474
+ {
475
+ "address_id": "ADDR0119",
476
+ "normalized_address": "039 narasimhan chowk, alwar, karnataka"
477
+ },
478
+ {
479
+ "address_id": "ADDR0120",
480
+ "normalized_address": "08/640 narula ganj, latur, gujarat"
481
+ },
482
+ {
483
+ "address_id": "ADDR0121",
484
+ "normalized_address": "03/39 verma marg, south dumdum, manipur"
485
+ },
486
+ {
487
+ "address_id": "ADDR0122",
488
+ "normalized_address": "01/713 dhaliwal nagar, cuttack, assam"
489
+ },
490
+ {
491
+ "address_id": "ADDR0123",
492
+ "normalized_address": "983 tripathi ganj, purnia, telangana"
493
+ },
494
+ {
495
+ "address_id": "ADDR0124",
496
+ "normalized_address": "42 konda ganj, belgaum, punjab"
497
+ },
498
+ {
499
+ "address_id": "ADDR0125",
500
+ "normalized_address": "449 jaggi chowk, ongole, assam"
501
+ },
502
+ {
503
+ "address_id": "ADDR0126",
504
+ "normalized_address": "h.no. 66 sinha street, bhusawal, mizoram"
505
+ },
506
+ {
507
+ "address_id": "ADDR0127",
508
+ "normalized_address": "62/70 sane marg, dewas, punjab"
509
+ },
510
+ {
511
+ "address_id": "ADDR0128",
512
+ "normalized_address": "28/24 sheth marg, malda, jharkhand"
513
+ },
514
+ {
515
+ "address_id": "ADDR0129",
516
+ "normalized_address": "89 bora chowk, bhavnagar, tamil nadu"
517
+ },
518
+ {
519
+ "address_id": "ADDR0130",
520
+ "normalized_address": "32 agrawal ganj, orai, telangana"
521
+ },
522
+ {
523
+ "address_id": "ADDR0131",
524
+ "normalized_address": "57/666 rajagopalan path, aizawl, rajasthan"
525
+ },
526
+ {
527
+ "address_id": "ADDR0132",
528
+ "normalized_address": "16/01 gaba marg, junagadh, himachal pradesh"
529
+ },
530
+ {
531
+ "address_id": "ADDR0133",
532
+ "normalized_address": "58/750 samra marg, latur, meghalaya"
533
+ },
534
+ {
535
+ "address_id": "ADDR0134",
536
+ "normalized_address": "h.no. 502 shah road, rohtak, haryana"
537
+ },
538
+ {
539
+ "address_id": "ADDR0135",
540
+ "normalized_address": "92/01 rajagopalan zila, dharmavaram, assam"
541
+ },
542
+ {
543
+ "address_id": "ADDR0136",
544
+ "normalized_address": "36/26 mittal road, dharmavaram, jharkhand"
545
+ },
546
+ {
547
+ "address_id": "ADDR0137",
548
+ "normalized_address": "61/195 luthra street, sikar, tamil nadu"
549
+ },
550
+ {
551
+ "address_id": "ADDR0138",
552
+ "normalized_address": "h.no. 02 agarwal chowk, coimbatore, tripura"
553
+ },
554
+ {
555
+ "address_id": "ADDR0139",
556
+ "normalized_address": "68/646 goda street, gaya, tamil nadu"
557
+ },
558
+ {
559
+ "address_id": "ADDR0140",
560
+ "normalized_address": "372 choudhary circle, tezpur, uttarakhand"
561
+ },
562
+ {
563
+ "address_id": "ADDR0141",
564
+ "normalized_address": "230 handa street, alappuzha, manipur"
565
+ },
566
+ {
567
+ "address_id": "ADDR0142",
568
+ "normalized_address": "91/214 purohit zila, bally, assam"
569
+ },
570
+ {
571
+ "address_id": "ADDR0143",
572
+ "normalized_address": "h.no. 09 sridhar ganj, salem, andhra pradesh"
573
+ },
574
+ {
575
+ "address_id": "ADDR0144",
576
+ "normalized_address": "h.no. 409 chatterjee nagar, nagaon, uttar pradesh"
577
+ },
578
+ {
579
+ "address_id": "ADDR0145",
580
+ "normalized_address": "h.no. 698 sunder road, naihati, punjab"
581
+ },
582
+ {
583
+ "address_id": "ADDR0146",
584
+ "normalized_address": "55 murty path, karimnagar, sikkim"
585
+ },
586
+ {
587
+ "address_id": "ADDR0147",
588
+ "normalized_address": "69 parekh nagar, salem, andhra pradesh"
589
+ },
590
+ {
591
+ "address_id": "ADDR0148",
592
+ "normalized_address": "62 das nagar, kulti, kerala"
593
+ },
594
+ {
595
+ "address_id": "ADDR0149",
596
+ "normalized_address": "97 sachdev circle, delhi, rajasthan"
597
+ },
598
+ {
599
+ "address_id": "ADDR0150",
600
+ "normalized_address": "h.no. 05 nanda nagar, muzaffarpur, punjab"
601
+ },
602
+ {
603
+ "address_id": "ADDR0151",
604
+ "normalized_address": "69 pall zila, fatehpur, sikkim"
605
+ },
606
+ {
607
+ "address_id": "ADDR0152",
608
+ "normalized_address": "16/493 venkatesh street, warangal, meghalaya"
609
+ },
610
+ {
611
+ "address_id": "ADDR0153",
612
+ "normalized_address": "h.no. 487 sehgal street, ongole, sikkim"
613
+ },
614
+ {
615
+ "address_id": "ADDR0154",
616
+ "normalized_address": "33/32 cherian zila, bhopal, goa"
617
+ },
618
+ {
619
+ "address_id": "ADDR0155",
620
+ "normalized_address": "h.no. 64 kurian ganj, amravati, sikkim"
621
+ },
622
+ {
623
+ "address_id": "ADDR0156",
624
+ "normalized_address": "871 kapur path, tiruvottiyur, madhya pradesh"
625
+ },
626
+ {
627
+ "address_id": "ADDR0157",
628
+ "normalized_address": "68/633 d’alia road, chandigarh, bihar"
629
+ },
630
+ {
631
+ "address_id": "ADDR0158",
632
+ "normalized_address": "h.no. 593 pillai zila, satara, madhya pradesh"
633
+ },
634
+ {
635
+ "address_id": "ADDR0159",
636
+ "normalized_address": "04/09 gupta circle, bhusawal, karnataka"
637
+ },
638
+ {
639
+ "address_id": "ADDR0160",
640
+ "normalized_address": "h.no. 28 kapadia, tenali, andhra pradesh"
641
+ },
642
+ {
643
+ "address_id": "ADDR0161",
644
+ "normalized_address": "75/24 tata ganj, siliguri, punjab"
645
+ },
646
+ {
647
+ "address_id": "ADDR0162",
648
+ "normalized_address": "h.no. 20 nagarajan, ambala, tamil nadu"
649
+ },
650
+ {
651
+ "address_id": "ADDR0163",
652
+ "normalized_address": "h.no. 497 talwar ganj, gandhinagar, arunachal pradesh"
653
+ },
654
+ {
655
+ "address_id": "ADDR0164",
656
+ "normalized_address": "h.no. 23 ben ganj, morbi, telangana"
657
+ },
658
+ {
659
+ "address_id": "ADDR0165",
660
+ "normalized_address": "42 ramanathan nagar, bhusawal, sikkim"
661
+ },
662
+ {
663
+ "address_id": "ADDR0166",
664
+ "normalized_address": "56 bora marg, ichalkaranji, tamil nadu"
665
+ },
666
+ {
667
+ "address_id": "ADDR0167",
668
+ "normalized_address": "861 naik street, avadi, manipur"
669
+ },
670
+ {
671
+ "address_id": "ADDR0168",
672
+ "normalized_address": "29/277 dhillon road, bellary, uttar pradesh"
673
+ },
674
+ {
675
+ "address_id": "ADDR0169",
676
+ "normalized_address": "30/591 puri street, gulbarga, arunachal pradesh"
677
+ },
678
+ {
679
+ "address_id": "ADDR0170",
680
+ "normalized_address": "h.no. 572 dalal nagar, guntakal, maharashtra"
681
+ },
682
+ {
683
+ "address_id": "ADDR0171",
684
+ "normalized_address": "630 khosla zila, unnao, jharkhand"
685
+ },
686
+ {
687
+ "address_id": "ADDR0172",
688
+ "normalized_address": "92/617 maharaj marg, arrah, andhra pradesh"
689
+ },
690
+ {
691
+ "address_id": "ADDR0173",
692
+ "normalized_address": "h.no. 111 upadhyay chowk, lucknow, odisha"
693
+ },
694
+ {
695
+ "address_id": "ADDR0174",
696
+ "normalized_address": "97/41 golla, karawal nagar, rajasthan"
697
+ },
698
+ {
699
+ "address_id": "ADDR0175",
700
+ "normalized_address": "64/921 bhatt path, machilipatnam, uttarakhand"
701
+ },
702
+ {
703
+ "address_id": "ADDR0176",
704
+ "normalized_address": "06/888 bhatt chowk, dhule, sikkim"
705
+ },
706
+ {
707
+ "address_id": "ADDR0177",
708
+ "normalized_address": "h.no. 292 sekhon nagar, bhilai, arunachal pradesh"
709
+ },
710
+ {
711
+ "address_id": "ADDR0178",
712
+ "normalized_address": "932 kunda zila, guwahati, bihar"
713
+ },
714
+ {
715
+ "address_id": "ADDR0179",
716
+ "normalized_address": "45/82 acharya marg, dewas, arunachal pradesh"
717
+ },
718
+ {
719
+ "address_id": "ADDR0180",
720
+ "normalized_address": "48/72 swaminathan, nagaon, kerala"
721
+ },
722
+ {
723
+ "address_id": "ADDR0181",
724
+ "normalized_address": "88/31 khalsa nagar, maheshtala, chhattisgarh"
725
+ },
726
+ {
727
+ "address_id": "ADDR0182",
728
+ "normalized_address": "h.no. 32 kalita, alwar, chhattisgarh"
729
+ },
730
+ {
731
+ "address_id": "ADDR0183",
732
+ "normalized_address": "96/184 raj road, silchar, manipur"
733
+ },
734
+ {
735
+ "address_id": "ADDR0184",
736
+ "normalized_address": "h.no. 462 jani circle, agartala, rajasthan"
737
+ },
738
+ {
739
+ "address_id": "ADDR0185",
740
+ "normalized_address": "35/989 saini street, satna, uttar pradesh"
741
+ },
742
+ {
743
+ "address_id": "ADDR0186",
744
+ "normalized_address": "60 kota, gopalpur, bihar"
745
+ },
746
+ {
747
+ "address_id": "ADDR0187",
748
+ "normalized_address": "18/61 chakraborty path, thoothukudi, tripura"
749
+ },
750
+ {
751
+ "address_id": "ADDR0188",
752
+ "normalized_address": "h.no. 23 anne road, burhanpur, haryana"
753
+ },
754
+ {
755
+ "address_id": "ADDR0189",
756
+ "normalized_address": "11 vyas street, giridih, uttarakhand"
757
+ },
758
+ {
759
+ "address_id": "ADDR0190",
760
+ "normalized_address": "18 manda path, narasaraopet, bihar"
761
+ },
762
+ {
763
+ "address_id": "ADDR0191",
764
+ "normalized_address": "h.no. 256 pai street, morena, odisha"
765
+ },
766
+ {
767
+ "address_id": "ADDR0192",
768
+ "normalized_address": "408 date street, burhanpur, odisha"
769
+ },
770
+ {
771
+ "address_id": "ADDR0193",
772
+ "normalized_address": "40/539 shankar marg, bahraich, kerala"
773
+ },
774
+ {
775
+ "address_id": "ADDR0194",
776
+ "normalized_address": "84 sangha road, farrukhabad, rajasthan"
777
+ },
778
+ {
779
+ "address_id": "ADDR0195",
780
+ "normalized_address": "h.no. 465 buch zila, mathura, assam"
781
+ },
782
+ {
783
+ "address_id": "ADDR0196",
784
+ "normalized_address": "901 tailor road, khandwa, sikkim"
785
+ },
786
+ {
787
+ "address_id": "ADDR0197",
788
+ "normalized_address": "27/78 dash circle, kanpur, punjab"
789
+ },
790
+ {
791
+ "address_id": "ADDR0198",
792
+ "normalized_address": "068 konda path, medininagar, manipur"
793
+ },
794
+ {
795
+ "address_id": "ADDR0199",
796
+ "normalized_address": "832 peri zila, kottayam, kerala"
797
+ },
798
+ {
799
+ "address_id": "ADDR0200",
800
+ "normalized_address": "h.no. 676 prakash street, sonipat, sikkim"
801
+ },
802
+ {
803
+ "address_id": "ADDR0201",
804
+ "normalized_address": "814 subramanian road, bilaspur, gujarat"
805
+ },
806
+ {
807
+ "address_id": "ADDR0202",
808
+ "normalized_address": "h.no. 963 varma road, madhyamgram, punjab"
809
+ },
810
+ {
811
+ "address_id": "ADDR0203",
812
+ "normalized_address": "05 sharma nagar, bihar sharif, kerala"
813
+ },
814
+ {
815
+ "address_id": "ADDR0204",
816
+ "normalized_address": "h.no. 052 pant marg, salem, maharashtra"
817
+ },
818
+ {
819
+ "address_id": "ADDR0205",
820
+ "normalized_address": "31 varkey circle, bulandshahr, bihar"
821
+ },
822
+ {
823
+ "address_id": "ADDR0206",
824
+ "normalized_address": "02 kara road, tenali, kerala"
825
+ },
826
+ {
827
+ "address_id": "ADDR0207",
828
+ "normalized_address": "h.no. 56 yadav street, moradabad, manipur"
829
+ },
830
+ {
831
+ "address_id": "ADDR0208",
832
+ "normalized_address": "72/256 parmer nagar, bhubaneswar, gujarat"
833
+ },
834
+ {
835
+ "address_id": "ADDR0209",
836
+ "normalized_address": "h.no. 02 sama marg, patna, goa"
837
+ },
838
+ {
839
+ "address_id": "ADDR0210",
840
+ "normalized_address": "37/416 tandon chowk, anantapur, tripura"
841
+ },
842
+ {
843
+ "address_id": "ADDR0211",
844
+ "normalized_address": "86 choudhary street, jaipur, chhattisgarh"
845
+ },
846
+ {
847
+ "address_id": "ADDR0212",
848
+ "normalized_address": "580 ben ganj, haldia, punjab"
849
+ },
850
+ {
851
+ "address_id": "ADDR0213",
852
+ "normalized_address": "06/744 butala chowk, shahjahanpur, punjab"
853
+ },
854
+ {
855
+ "address_id": "ADDR0214",
856
+ "normalized_address": "823 contractor road, shahjahanpur, sikkim"
857
+ },
858
+ {
859
+ "address_id": "ADDR0215",
860
+ "normalized_address": "37 thakkar zila, maheshtala, west bengal"
861
+ },
862
+ {
863
+ "address_id": "ADDR0216",
864
+ "normalized_address": "23/12 barad marg, nellore, chhattisgarh"
865
+ },
866
+ {
867
+ "address_id": "ADDR0217",
868
+ "normalized_address": "09 chand ganj, nadiad, maharashtra"
869
+ },
870
+ {
871
+ "address_id": "ADDR0218",
872
+ "normalized_address": "43/35 srinivas marg, sasaram, maharashtra"
873
+ },
874
+ {
875
+ "address_id": "ADDR0219",
876
+ "normalized_address": "04/145 panchal road, shahjahanpur, karnataka"
877
+ },
878
+ {
879
+ "address_id": "ADDR0220",
880
+ "normalized_address": "h.no. 200 nagy nagar, baranagar, meghalaya"
881
+ },
882
+ {
883
+ "address_id": "ADDR0221",
884
+ "normalized_address": "h.no. 91 goyal marg, pallavaram, kerala"
885
+ },
886
+ {
887
+ "address_id": "ADDR0222",
888
+ "normalized_address": "h.no. 41 arya zila, bathinda, kerala"
889
+ },
890
+ {
891
+ "address_id": "ADDR0223",
892
+ "normalized_address": "59/895 majumdar ganj, bhimavaram, madhya pradesh"
893
+ },
894
+ {
895
+ "address_id": "ADDR0224",
896
+ "normalized_address": "h.no. 129 barman zila, nellore, tamil nadu"
897
+ },
898
+ {
899
+ "address_id": "ADDR0225",
900
+ "normalized_address": "11 oza circle, bhalswa jahangir pur, tripura"
901
+ },
902
+ {
903
+ "address_id": "ADDR0226",
904
+ "normalized_address": "820 handa street, jamalpur, tamil nadu"
905
+ },
906
+ {
907
+ "address_id": "ADDR0227",
908
+ "normalized_address": "h.no. 85 sen path, junagadh, assam"
909
+ },
910
+ {
911
+ "address_id": "ADDR0228",
912
+ "normalized_address": "68 bains circle, khora , himachal pradesh"
913
+ },
914
+ {
915
+ "address_id": "ADDR0229",
916
+ "normalized_address": "24/090 rao ganj, katihar, west bengal"
917
+ },
918
+ {
919
+ "address_id": "ADDR0230",
920
+ "normalized_address": "12/78 ramakrishnan marg, medininagar, himachal pradesh"
921
+ },
922
+ {
923
+ "address_id": "ADDR0231",
924
+ "normalized_address": "202 kamdar, anand, manipur"
925
+ },
926
+ {
927
+ "address_id": "ADDR0232",
928
+ "normalized_address": "84/43 loyal road, tirunelveli, haryana"
929
+ },
930
+ {
931
+ "address_id": "ADDR0233",
932
+ "normalized_address": "h.no. 834 divan nagar, guntur, himachal pradesh"
933
+ },
934
+ {
935
+ "address_id": "ADDR0234",
936
+ "normalized_address": "h.no. 338 agarwal zila, mahbubnagar, kerala"
937
+ },
938
+ {
939
+ "address_id": "ADDR0235",
940
+ "normalized_address": "h.no. 18 deol road, pudukkottai, jharkhand"
941
+ },
942
+ {
943
+ "address_id": "ADDR0236",
944
+ "normalized_address": "h.no. 00 kanda path, belgaum, himachal pradesh"
945
+ },
946
+ {
947
+ "address_id": "ADDR0237",
948
+ "normalized_address": "h.no. 891 kurian marg, mango, haryana"
949
+ },
950
+ {
951
+ "address_id": "ADDR0238",
952
+ "normalized_address": "h.no. 43 rana, amroha, haryana"
953
+ },
954
+ {
955
+ "address_id": "ADDR0239",
956
+ "normalized_address": "65/271 golla chowk, udaipur, uttarakhand"
957
+ },
958
+ {
959
+ "address_id": "ADDR0240",
960
+ "normalized_address": "397 manda street, tirupati, goa"
961
+ },
962
+ {
963
+ "address_id": "ADDR0241",
964
+ "normalized_address": "693 dhar zila, purnia, haryana"
965
+ },
966
+ {
967
+ "address_id": "ADDR0242",
968
+ "normalized_address": "61 devan circle, madanapalle, bihar"
969
+ },
970
+ {
971
+ "address_id": "ADDR0243",
972
+ "normalized_address": "60 arya zila, mira-bhayandar, haryana"
973
+ },
974
+ {
975
+ "address_id": "ADDR0244",
976
+ "normalized_address": "h.no. 429 andra nagar, hosur, telangana"
977
+ },
978
+ {
979
+ "address_id": "ADDR0245",
980
+ "normalized_address": "h.no. 13 srinivasan street, ujjain, jharkhand"
981
+ },
982
+ {
983
+ "address_id": "ADDR0246",
984
+ "normalized_address": "h.no. 72 bhalla ganj, tinsukia, bihar"
985
+ },
986
+ {
987
+ "address_id": "ADDR0247",
988
+ "normalized_address": "h.no. 018 chauhan road, ajmer, andhra pradesh"
989
+ },
990
+ {
991
+ "address_id": "ADDR0248",
992
+ "normalized_address": "036 din marg, baranagar, haryana"
993
+ },
994
+ {
995
+ "address_id": "ADDR0249",
996
+ "normalized_address": "61/721 chaudry ganj, raurkela industrial township, assam"
997
+ },
998
+ {
999
+ "address_id": "ADDR0250",
1000
+ "normalized_address": "51/51 dora nagar, haldia, mizoram"
1001
+ },
1002
+ {
1003
+ "address_id": "ADDR0251",
1004
+ "normalized_address": "h.no. 92 doctor marg, baranagar, kerala"
1005
+ },
1006
+ {
1007
+ "address_id": "ADDR0252",
1008
+ "normalized_address": "h.no. 475 bose road, jalgaon, tripura"
1009
+ },
1010
+ {
1011
+ "address_id": "ADDR0253",
1012
+ "normalized_address": "25/267 bhavsar chowk, gangtok, maharashtra"
1013
+ },
1014
+ {
1015
+ "address_id": "ADDR0254",
1016
+ "normalized_address": "42/152 rajan street, imphal, meghalaya"
1017
+ },
1018
+ {
1019
+ "address_id": "ADDR0255",
1020
+ "normalized_address": "76 nath, sambalpur, madhya pradesh"
1021
+ },
1022
+ {
1023
+ "address_id": "ADDR0256",
1024
+ "normalized_address": "87 walla circle, katihar, nagaland"
1025
+ },
1026
+ {
1027
+ "address_id": "ADDR0257",
1028
+ "normalized_address": "619 vyas marg, chandigarh, haryana"
1029
+ },
1030
+ {
1031
+ "address_id": "ADDR0258",
1032
+ "normalized_address": "h.no. 03 patil nagar, guntur, bihar"
1033
+ },
1034
+ {
1035
+ "address_id": "ADDR0259",
1036
+ "normalized_address": "h.no. 552 sharma road, suryapet, maharashtra"
1037
+ },
1038
+ {
1039
+ "address_id": "ADDR0260",
1040
+ "normalized_address": "330 dalal street, burhanpur, telangana"
1041
+ },
1042
+ {
1043
+ "address_id": "ADDR0261",
1044
+ "normalized_address": "h.no. 650 borah zila, hindupur, west bengal"
1045
+ },
1046
+ {
1047
+ "address_id": "ADDR0262",
1048
+ "normalized_address": "025 brar street, solapur, andhra pradesh"
1049
+ },
1050
+ {
1051
+ "address_id": "ADDR0263",
1052
+ "normalized_address": "62/27 wable street, nandyal, maharashtra"
1053
+ },
1054
+ {
1055
+ "address_id": "ADDR0264",
1056
+ "normalized_address": "57/041 bhasin street, mahbubnagar, tripura"
1057
+ },
1058
+ {
1059
+ "address_id": "ADDR0265",
1060
+ "normalized_address": "820 sampath marg, begusarai, tamil nadu"
1061
+ },
1062
+ {
1063
+ "address_id": "ADDR0266",
1064
+ "normalized_address": "h.no. 98 karan road, sangli-miraj & kupwad, himachal pradesh"
1065
+ },
1066
+ {
1067
+ "address_id": "ADDR0267",
1068
+ "normalized_address": "h.no. 862 rattan ganj, muzaffarnagar, madhya pradesh"
1069
+ },
1070
+ {
1071
+ "address_id": "ADDR0268",
1072
+ "normalized_address": "891 sood chowk, machilipatnam, uttarakhand"
1073
+ },
1074
+ {
1075
+ "address_id": "ADDR0269",
1076
+ "normalized_address": "20 samra path, gopalpur, bihar"
1077
+ },
1078
+ {
1079
+ "address_id": "ADDR0270",
1080
+ "normalized_address": "h.no. 159 bala path, ichalkaranji, goa"
1081
+ },
1082
+ {
1083
+ "address_id": "ADDR0271",
1084
+ "normalized_address": "11/39 more nagar, suryapet, gujarat"
1085
+ },
1086
+ {
1087
+ "address_id": "ADDR0272",
1088
+ "normalized_address": "03/419 basu, ranchi, arunachal pradesh"
1089
+ },
1090
+ {
1091
+ "address_id": "ADDR0273",
1092
+ "normalized_address": "h.no. 14 parikh nagar, sikar, mizoram"
1093
+ },
1094
+ {
1095
+ "address_id": "ADDR0274",
1096
+ "normalized_address": "565 chatterjee street, vellore, telangana"
1097
+ },
1098
+ {
1099
+ "address_id": "ADDR0275",
1100
+ "normalized_address": "18/20 raja, dharmavaram, west bengal"
1101
+ },
1102
+ {
1103
+ "address_id": "ADDR0276",
1104
+ "normalized_address": "h.no. 422 brar chowk, ahmednagar, gujarat"
1105
+ },
1106
+ {
1107
+ "address_id": "ADDR0277",
1108
+ "normalized_address": "22/889 om circle, mira-bhayandar, karnataka"
1109
+ },
1110
+ {
1111
+ "address_id": "ADDR0278",
1112
+ "normalized_address": "h.no. 59 baral marg, mathura, gujarat"
1113
+ },
1114
+ {
1115
+ "address_id": "ADDR0279",
1116
+ "normalized_address": "91/29 chatterjee chowk, asansol, tripura"
1117
+ },
1118
+ {
1119
+ "address_id": "ADDR0280",
1120
+ "normalized_address": "h.no. 81 chandra chowk, satara, goa"
1121
+ },
1122
+ {
1123
+ "address_id": "ADDR0281",
1124
+ "normalized_address": "h.no. 21 patla road, hubli–dharwad, assam"
1125
+ },
1126
+ {
1127
+ "address_id": "ADDR0282",
1128
+ "normalized_address": "747 sathe road, amravati, odisha"
1129
+ },
1130
+ {
1131
+ "address_id": "ADDR0283",
1132
+ "normalized_address": "05 rattan road, udupi, sikkim"
1133
+ },
1134
+ {
1135
+ "address_id": "ADDR0284",
1136
+ "normalized_address": "758 murthy, gwalior, sikkim"
1137
+ },
1138
+ {
1139
+ "address_id": "ADDR0285",
1140
+ "normalized_address": "87 basak road, sambalpur, tamil nadu"
1141
+ },
1142
+ {
1143
+ "address_id": "ADDR0286",
1144
+ "normalized_address": "02/14 dutt marg, srinagar, haryana"
1145
+ },
1146
+ {
1147
+ "address_id": "ADDR0287",
1148
+ "normalized_address": "71 shukla path, deoghar, uttarakhand"
1149
+ },
1150
+ {
1151
+ "address_id": "ADDR0288",
1152
+ "normalized_address": "11 chokshi street, bhiwani, gujarat"
1153
+ },
1154
+ {
1155
+ "address_id": "ADDR0289",
1156
+ "normalized_address": "51 oommen path, rajpur sonarpur, tamil nadu"
1157
+ },
1158
+ {
1159
+ "address_id": "ADDR0290",
1160
+ "normalized_address": "71/470 dugal circle, ulhasnagar, rajasthan"
1161
+ },
1162
+ {
1163
+ "address_id": "ADDR0291",
1164
+ "normalized_address": "590 rai, mirzapur, uttar pradesh"
1165
+ },
1166
+ {
1167
+ "address_id": "ADDR0292",
1168
+ "normalized_address": "73 kakar nagar, bhusawal, himachal pradesh"
1169
+ },
1170
+ {
1171
+ "address_id": "ADDR0293",
1172
+ "normalized_address": "h.no. 21 biswas street, machilipatnam, assam"
1173
+ },
1174
+ {
1175
+ "address_id": "ADDR0294",
1176
+ "normalized_address": "h.no. 76 kala circle, udaipur, uttarakhand"
1177
+ },
1178
+ {
1179
+ "address_id": "ADDR0295",
1180
+ "normalized_address": "h.no. 165 gala road, karnal, maharashtra"
1181
+ },
1182
+ {
1183
+ "address_id": "ADDR0296",
1184
+ "normalized_address": "78 sarkar street, aurangabad, madhya pradesh"
1185
+ },
1186
+ {
1187
+ "address_id": "ADDR0297",
1188
+ "normalized_address": "h.no. 893 dhillon zila, ratlam, andhra pradesh"
1189
+ },
1190
+ {
1191
+ "address_id": "ADDR0298",
1192
+ "normalized_address": "727 comar path, firozabad, mizoram"
1193
+ },
1194
+ {
1195
+ "address_id": "ADDR0299",
1196
+ "normalized_address": "07/279 bansal zila, maheshtala, maharashtra"
1197
+ },
1198
+ {
1199
+ "address_id": "ADDR0300",
1200
+ "normalized_address": "82/963 kadakia path, gangtok, andhra pradesh"
1201
+ },
1202
+ {
1203
+ "address_id": "ADDR0301",
1204
+ "normalized_address": "h.no. 50 mohanty road, panchkula, rajasthan"
1205
+ },
1206
+ {
1207
+ "address_id": "ADDR0302",
1208
+ "normalized_address": "66 pandey path, nagaon, tripura"
1209
+ },
1210
+ {
1211
+ "address_id": "ADDR0303",
1212
+ "normalized_address": "h.no. 12 sangha chowk, belgaum, nagaland"
1213
+ },
1214
+ {
1215
+ "address_id": "ADDR0304",
1216
+ "normalized_address": "28/66 sarkar road, udupi, west bengal"
1217
+ },
1218
+ {
1219
+ "address_id": "ADDR0305",
1220
+ "normalized_address": "h.no. 111 sabharwal nagar, mehsana, telangana"
1221
+ },
1222
+ {
1223
+ "address_id": "ADDR0306",
1224
+ "normalized_address": "81/78 bali, bidar, gujarat"
1225
+ },
1226
+ {
1227
+ "address_id": "ADDR0307",
1228
+ "normalized_address": "786 hayre zila, amravati, kerala"
1229
+ },
1230
+ {
1231
+ "address_id": "ADDR0308",
1232
+ "normalized_address": "66 prasad nagar, guna, jharkhand"
1233
+ },
1234
+ {
1235
+ "address_id": "ADDR0309",
1236
+ "normalized_address": "h.no. 44 khurana marg, vijayanagaram, arunachal pradesh"
1237
+ },
1238
+ {
1239
+ "address_id": "ADDR0310",
1240
+ "normalized_address": "h.no. 025 nadkarni circle, tirunelveli, odisha"
1241
+ },
1242
+ {
1243
+ "address_id": "ADDR0311",
1244
+ "normalized_address": "38 hans marg, faridabad, uttar pradesh"
1245
+ },
1246
+ {
1247
+ "address_id": "ADDR0312",
1248
+ "normalized_address": "62/62 nadkarni marg, junagadh, haryana"
1249
+ },
1250
+ {
1251
+ "address_id": "ADDR0313",
1252
+ "normalized_address": "h.no. 281 ramanathan, begusarai, goa"
1253
+ },
1254
+ {
1255
+ "address_id": "ADDR0314",
1256
+ "normalized_address": "h.no. 31 bal zila, hyderabad, rajasthan"
1257
+ },
1258
+ {
1259
+ "address_id": "ADDR0315",
1260
+ "normalized_address": "20/98 raja zila, bikaner, manipur"
1261
+ },
1262
+ {
1263
+ "address_id": "ADDR0316",
1264
+ "normalized_address": "52/334 wason ganj, burhanpur, tripura"
1265
+ },
1266
+ {
1267
+ "address_id": "ADDR0317",
1268
+ "normalized_address": "h.no. 04 dhawan zila, adoni, west bengal"
1269
+ },
1270
+ {
1271
+ "address_id": "ADDR0318",
1272
+ "normalized_address": "h.no. 179 chhabra zila, ichalkaranji, himachal pradesh"
1273
+ },
1274
+ {
1275
+ "address_id": "ADDR0319",
1276
+ "normalized_address": "117 johal path, salem, meghalaya"
1277
+ },
1278
+ {
1279
+ "address_id": "ADDR0320",
1280
+ "normalized_address": "h.no. 617 kade zila, gandhinagar, karnataka"
1281
+ },
1282
+ {
1283
+ "address_id": "ADDR0321",
1284
+ "normalized_address": "72/240 balay street, tiruvottiyur, kerala"
1285
+ },
1286
+ {
1287
+ "address_id": "ADDR0322",
1288
+ "normalized_address": "99 ahluwalia circle, nagaon, telangana"
1289
+ },
1290
+ {
1291
+ "address_id": "ADDR0323",
1292
+ "normalized_address": "h.no. 13 balan, kadapa, mizoram"
1293
+ },
1294
+ {
1295
+ "address_id": "ADDR0324",
1296
+ "normalized_address": "351 balan ganj, durgapur, haryana"
1297
+ },
1298
+ {
1299
+ "address_id": "ADDR0325",
1300
+ "normalized_address": "h.no. 244 iyengar chowk, karnal, tamil nadu"
1301
+ },
1302
+ {
1303
+ "address_id": "ADDR0326",
1304
+ "normalized_address": "47/76 ratta circle, chandrapur, goa"
1305
+ },
1306
+ {
1307
+ "address_id": "ADDR0327",
1308
+ "normalized_address": "461 muni, tiruppur, telangana"
1309
+ },
1310
+ {
1311
+ "address_id": "ADDR0328",
1312
+ "normalized_address": "67 magar marg, moradabad, punjab"
1313
+ },
1314
+ {
1315
+ "address_id": "ADDR0329",
1316
+ "normalized_address": "63 pandit, rajkot, manipur"
1317
+ },
1318
+ {
1319
+ "address_id": "ADDR0330",
1320
+ "normalized_address": "084 rout, mahbubnagar, uttarakhand"
1321
+ },
1322
+ {
1323
+ "address_id": "ADDR0331",
1324
+ "normalized_address": "85/601 naidu street, gaya, kerala"
1325
+ },
1326
+ {
1327
+ "address_id": "ADDR0332",
1328
+ "normalized_address": "135 dhillon path, bhilai, andhra pradesh"
1329
+ },
1330
+ {
1331
+ "address_id": "ADDR0333",
1332
+ "normalized_address": "h.no. 05 shroff ganj, muzaffarpur, nagaland"
1333
+ },
1334
+ {
1335
+ "address_id": "ADDR0334",
1336
+ "normalized_address": "h.no. 649 vyas ganj, kota, nagaland"
1337
+ },
1338
+ {
1339
+ "address_id": "ADDR0335",
1340
+ "normalized_address": "h.no. 91 sama, ratlam, uttarakhand"
1341
+ },
1342
+ {
1343
+ "address_id": "ADDR0336",
1344
+ "normalized_address": "h.no. 33 tak nagar, ranchi, himachal pradesh"
1345
+ },
1346
+ {
1347
+ "address_id": "ADDR0337",
1348
+ "normalized_address": "11 garg chowk, gudivada, madhya pradesh"
1349
+ },
1350
+ {
1351
+ "address_id": "ADDR0338",
1352
+ "normalized_address": "904 agrawal nagar, anantapuram, chhattisgarh"
1353
+ },
1354
+ {
1355
+ "address_id": "ADDR0339",
1356
+ "normalized_address": "08 baral circle, pune, assam"
1357
+ },
1358
+ {
1359
+ "address_id": "ADDR0340",
1360
+ "normalized_address": "16 maharaj road, sri ganganagar, madhya pradesh"
1361
+ },
1362
+ {
1363
+ "address_id": "ADDR0341",
1364
+ "normalized_address": "h.no. 857 batta, bihar sharif, tamil nadu"
1365
+ },
1366
+ {
1367
+ "address_id": "ADDR0342",
1368
+ "normalized_address": "71 chauhan path, rajahmundry, arunachal pradesh"
1369
+ },
1370
+ {
1371
+ "address_id": "ADDR0343",
1372
+ "normalized_address": "h.no. 215 kara marg, hubli–dharwad, gujarat"
1373
+ },
1374
+ {
1375
+ "address_id": "ADDR0344",
1376
+ "normalized_address": "58/004 choudhry nagar, avadi, assam"
1377
+ },
1378
+ {
1379
+ "address_id": "ADDR0345",
1380
+ "normalized_address": "h.no. 59 madan marg, durgapur, odisha"
1381
+ },
1382
+ {
1383
+ "address_id": "ADDR0346",
1384
+ "normalized_address": "12 divan path, amroha, sikkim"
1385
+ },
1386
+ {
1387
+ "address_id": "ADDR0347",
1388
+ "normalized_address": "h.no. 883 sridhar, parbhani, karnataka"
1389
+ },
1390
+ {
1391
+ "address_id": "ADDR0348",
1392
+ "normalized_address": "73/144 pandit zila, tiruppur, tamil nadu"
1393
+ },
1394
+ {
1395
+ "address_id": "ADDR0349",
1396
+ "normalized_address": "h.no. 170 arya ganj, bhind, uttarakhand"
1397
+ },
1398
+ {
1399
+ "address_id": "ADDR0350",
1400
+ "normalized_address": "37 oak zila, gudivada, tripura"
1401
+ },
1402
+ {
1403
+ "address_id": "ADDR0351",
1404
+ "normalized_address": "12 fraud lane, mumbai, maharashtra"
1405
+ },
1406
+ {
1407
+ "address_id": "ADDR0352",
1408
+ "normalized_address": "45 ring road, delhi, delhi"
1409
+ },
1410
+ {
1411
+ "address_id": "ADDR0353",
1412
+ "normalized_address": "78 scheme nagar, bengaluru, karnataka"
1413
+ },
1414
+ {
1415
+ "address_id": "ADDR0354",
1416
+ "normalized_address": "23 boundary blvd, pune, maharashtra"
1417
+ },
1418
+ {
1419
+ "address_id": "ADDR0355",
1420
+ "normalized_address": "56 ambiguous avenue, hyderabad, telangana"
1421
+ },
1422
+ {
1423
+ "address_id": "ADDR0356",
1424
+ "normalized_address": "89 liminal street, chennai, tamil_nadu"
1425
+ },
1426
+ {
1427
+ "address_id": "ADDR0357",
1428
+ "normalized_address": "33 ordinary street, jaipur, rajasthan"
1429
+ }
1430
+ ]
data/synthetic/candidate_clusters.json ADDED
@@ -0,0 +1,784 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "cluster_id": "CAND01",
4
+ "customer_ids": [
5
+ "CUST0041",
6
+ "CUST0042",
7
+ "CUST0043",
8
+ "CUST0044"
9
+ ],
10
+ "size": 4,
11
+ "strong_link_count": 6,
12
+ "weak_link_count": 0,
13
+ "formation_type": "strong_anchored",
14
+ "method": "connected_components"
15
+ },
16
+ {
17
+ "cluster_id": "CAND02",
18
+ "customer_ids": [
19
+ "CUST0009",
20
+ "CUST0010",
21
+ "CUST0011",
22
+ "CUST0012",
23
+ "CUST0013"
24
+ ],
25
+ "size": 5,
26
+ "strong_link_count": 10,
27
+ "weak_link_count": 0,
28
+ "formation_type": "strong_anchored",
29
+ "method": "connected_components"
30
+ },
31
+ {
32
+ "cluster_id": "CAND03",
33
+ "customer_ids": [
34
+ "CUST0001",
35
+ "CUST0002",
36
+ "CUST0003"
37
+ ],
38
+ "size": 3,
39
+ "strong_link_count": 3,
40
+ "weak_link_count": 0,
41
+ "formation_type": "strong_anchored",
42
+ "method": "connected_components"
43
+ },
44
+ {
45
+ "cluster_id": "CAND04",
46
+ "customer_ids": [
47
+ "CUST0014",
48
+ "CUST0015",
49
+ "CUST0016",
50
+ "CUST0017",
51
+ "CUST0018"
52
+ ],
53
+ "size": 5,
54
+ "strong_link_count": 10,
55
+ "weak_link_count": 0,
56
+ "formation_type": "strong_anchored",
57
+ "method": "connected_components"
58
+ },
59
+ {
60
+ "cluster_id": "CAND05",
61
+ "customer_ids": [
62
+ "CUST0033",
63
+ "CUST0034",
64
+ "CUST0035"
65
+ ],
66
+ "size": 3,
67
+ "strong_link_count": 3,
68
+ "weak_link_count": 0,
69
+ "formation_type": "strong_anchored",
70
+ "method": "connected_components"
71
+ },
72
+ {
73
+ "cluster_id": "CAND06",
74
+ "customer_ids": [
75
+ "CUST0036",
76
+ "CUST0037",
77
+ "CUST0038",
78
+ "CUST0039",
79
+ "CUST0040"
80
+ ],
81
+ "size": 5,
82
+ "strong_link_count": 10,
83
+ "weak_link_count": 0,
84
+ "formation_type": "strong_anchored",
85
+ "method": "connected_components"
86
+ },
87
+ {
88
+ "cluster_id": "CAND07",
89
+ "customer_ids": [
90
+ "CUST0019",
91
+ "CUST0020",
92
+ "CUST0021",
93
+ "CUST0022",
94
+ "CUST0023"
95
+ ],
96
+ "size": 5,
97
+ "strong_link_count": 10,
98
+ "weak_link_count": 0,
99
+ "formation_type": "strong_anchored",
100
+ "method": "connected_components"
101
+ },
102
+ {
103
+ "cluster_id": "CAND08",
104
+ "customer_ids": [
105
+ "CUST0501",
106
+ "CUST0502",
107
+ "CUST0503"
108
+ ],
109
+ "size": 3,
110
+ "strong_link_count": 3,
111
+ "weak_link_count": 0,
112
+ "formation_type": "strong_anchored",
113
+ "method": "connected_components"
114
+ },
115
+ {
116
+ "cluster_id": "CAND09",
117
+ "customer_ids": [
118
+ "CUST0024",
119
+ "CUST0025",
120
+ "CUST0026",
121
+ "CUST0027",
122
+ "CUST0028",
123
+ "CUST0029"
124
+ ],
125
+ "size": 6,
126
+ "strong_link_count": 15,
127
+ "weak_link_count": 0,
128
+ "formation_type": "strong_anchored",
129
+ "method": "connected_components"
130
+ },
131
+ {
132
+ "cluster_id": "CAND10",
133
+ "customer_ids": [
134
+ "CUST0004",
135
+ "CUST0005",
136
+ "CUST0006",
137
+ "CUST0007",
138
+ "CUST0008"
139
+ ],
140
+ "size": 5,
141
+ "strong_link_count": 7,
142
+ "weak_link_count": 0,
143
+ "formation_type": "strong_anchored",
144
+ "method": "connected_components"
145
+ },
146
+ {
147
+ "cluster_id": "CAND11",
148
+ "customer_ids": [
149
+ "CUST0030",
150
+ "CUST0031",
151
+ "CUST0032"
152
+ ],
153
+ "size": 3,
154
+ "strong_link_count": 2,
155
+ "weak_link_count": 0,
156
+ "formation_type": "strong_anchored",
157
+ "method": "connected_components"
158
+ },
159
+ {
160
+ "cluster_id": "CAND12",
161
+ "customer_ids": [
162
+ "CUST0001",
163
+ "CUST0019",
164
+ "CUST0040",
165
+ "CUST0126",
166
+ "CUST0138",
167
+ "CUST0168",
168
+ "CUST0211",
169
+ "CUST0219",
170
+ "CUST0258",
171
+ "CUST0281",
172
+ "CUST0292",
173
+ "CUST0322",
174
+ "CUST0325",
175
+ "CUST0371",
176
+ "CUST0373",
177
+ "CUST0400",
178
+ "CUST0419",
179
+ "CUST0465"
180
+ ],
181
+ "size": 18,
182
+ "strong_link_count": 0,
183
+ "weak_link_count": 41,
184
+ "formation_type": "weak_only",
185
+ "method": "louvain"
186
+ },
187
+ {
188
+ "cluster_id": "CAND13",
189
+ "customer_ids": [
190
+ "CUST0002",
191
+ "CUST0009",
192
+ "CUST0020",
193
+ "CUST0041",
194
+ "CUST0064",
195
+ "CUST0073",
196
+ "CUST0074",
197
+ "CUST0080",
198
+ "CUST0081",
199
+ "CUST0086",
200
+ "CUST0089",
201
+ "CUST0103",
202
+ "CUST0162",
203
+ "CUST0167",
204
+ "CUST0173",
205
+ "CUST0181",
206
+ "CUST0226",
207
+ "CUST0240",
208
+ "CUST0253",
209
+ "CUST0262",
210
+ "CUST0272",
211
+ "CUST0286",
212
+ "CUST0287",
213
+ "CUST0324",
214
+ "CUST0331",
215
+ "CUST0333",
216
+ "CUST0369",
217
+ "CUST0392",
218
+ "CUST0393",
219
+ "CUST0398",
220
+ "CUST0406",
221
+ "CUST0410",
222
+ "CUST0411",
223
+ "CUST0413",
224
+ "CUST0415",
225
+ "CUST0423",
226
+ "CUST0446",
227
+ "CUST0468",
228
+ "CUST0476",
229
+ "CUST0478",
230
+ "CUST0485"
231
+ ],
232
+ "size": 41,
233
+ "strong_link_count": 0,
234
+ "weak_link_count": 146,
235
+ "formation_type": "weak_only",
236
+ "method": "louvain"
237
+ },
238
+ {
239
+ "cluster_id": "CAND14",
240
+ "customer_ids": [
241
+ "CUST0003",
242
+ "CUST0012",
243
+ "CUST0043",
244
+ "CUST0077",
245
+ "CUST0088",
246
+ "CUST0098",
247
+ "CUST0109",
248
+ "CUST0125",
249
+ "CUST0139",
250
+ "CUST0153",
251
+ "CUST0159",
252
+ "CUST0166",
253
+ "CUST0180",
254
+ "CUST0201",
255
+ "CUST0209",
256
+ "CUST0245",
257
+ "CUST0261",
258
+ "CUST0265",
259
+ "CUST0273",
260
+ "CUST0277",
261
+ "CUST0297",
262
+ "CUST0315",
263
+ "CUST0334",
264
+ "CUST0347",
265
+ "CUST0381",
266
+ "CUST0405",
267
+ "CUST0412",
268
+ "CUST0428",
269
+ "CUST0429",
270
+ "CUST0462",
271
+ "CUST0471"
272
+ ],
273
+ "size": 31,
274
+ "strong_link_count": 0,
275
+ "weak_link_count": 84,
276
+ "formation_type": "weak_only",
277
+ "method": "louvain"
278
+ },
279
+ {
280
+ "cluster_id": "CAND15",
281
+ "customer_ids": [
282
+ "CUST0004",
283
+ "CUST0008",
284
+ "CUST0027",
285
+ "CUST0030",
286
+ "CUST0042",
287
+ "CUST0063",
288
+ "CUST0075",
289
+ "CUST0087",
290
+ "CUST0110",
291
+ "CUST0117",
292
+ "CUST0120",
293
+ "CUST0133",
294
+ "CUST0135",
295
+ "CUST0137",
296
+ "CUST0144",
297
+ "CUST0152",
298
+ "CUST0154",
299
+ "CUST0163",
300
+ "CUST0170",
301
+ "CUST0178",
302
+ "CUST0179",
303
+ "CUST0195",
304
+ "CUST0198",
305
+ "CUST0208",
306
+ "CUST0210",
307
+ "CUST0221",
308
+ "CUST0228",
309
+ "CUST0231",
310
+ "CUST0237",
311
+ "CUST0246",
312
+ "CUST0251",
313
+ "CUST0254",
314
+ "CUST0266",
315
+ "CUST0282",
316
+ "CUST0295",
317
+ "CUST0300",
318
+ "CUST0303",
319
+ "CUST0304",
320
+ "CUST0335",
321
+ "CUST0355",
322
+ "CUST0365",
323
+ "CUST0399",
324
+ "CUST0420",
325
+ "CUST0425",
326
+ "CUST0470",
327
+ "CUST0483",
328
+ "CUST0486",
329
+ "CUST0487",
330
+ "CUST0491",
331
+ "CUST0492",
332
+ "CUST0498"
333
+ ],
334
+ "size": 51,
335
+ "strong_link_count": 1,
336
+ "weak_link_count": 167,
337
+ "formation_type": "weak_only",
338
+ "method": "louvain"
339
+ },
340
+ {
341
+ "cluster_id": "CAND16",
342
+ "customer_ids": [
343
+ "CUST0005",
344
+ "CUST0022",
345
+ "CUST0028",
346
+ "CUST0038",
347
+ "CUST0102",
348
+ "CUST0111",
349
+ "CUST0113",
350
+ "CUST0140",
351
+ "CUST0161",
352
+ "CUST0171",
353
+ "CUST0177",
354
+ "CUST0202",
355
+ "CUST0222",
356
+ "CUST0235",
357
+ "CUST0252",
358
+ "CUST0257",
359
+ "CUST0263",
360
+ "CUST0288",
361
+ "CUST0293",
362
+ "CUST0299",
363
+ "CUST0308",
364
+ "CUST0311",
365
+ "CUST0353",
366
+ "CUST0357",
367
+ "CUST0364",
368
+ "CUST0370",
369
+ "CUST0383",
370
+ "CUST0386",
371
+ "CUST0387",
372
+ "CUST0391",
373
+ "CUST0396",
374
+ "CUST0438",
375
+ "CUST0442",
376
+ "CUST0456",
377
+ "CUST0460",
378
+ "CUST0488",
379
+ "CUST0490",
380
+ "CUST0495",
381
+ "CUST0496",
382
+ "CUST0500"
383
+ ],
384
+ "size": 40,
385
+ "strong_link_count": 0,
386
+ "weak_link_count": 112,
387
+ "formation_type": "weak_only",
388
+ "method": "louvain"
389
+ },
390
+ {
391
+ "cluster_id": "CAND17",
392
+ "customer_ids": [
393
+ "CUST0006",
394
+ "CUST0011",
395
+ "CUST0024",
396
+ "CUST0025",
397
+ "CUST0033",
398
+ "CUST0044",
399
+ "CUST0079",
400
+ "CUST0084",
401
+ "CUST0101",
402
+ "CUST0107",
403
+ "CUST0108",
404
+ "CUST0114",
405
+ "CUST0132",
406
+ "CUST0145",
407
+ "CUST0157",
408
+ "CUST0172",
409
+ "CUST0227",
410
+ "CUST0244",
411
+ "CUST0255",
412
+ "CUST0256",
413
+ "CUST0267",
414
+ "CUST0271",
415
+ "CUST0284",
416
+ "CUST0291",
417
+ "CUST0296",
418
+ "CUST0316",
419
+ "CUST0319",
420
+ "CUST0329",
421
+ "CUST0336",
422
+ "CUST0342",
423
+ "CUST0348",
424
+ "CUST0349",
425
+ "CUST0367",
426
+ "CUST0375",
427
+ "CUST0389",
428
+ "CUST0407",
429
+ "CUST0455",
430
+ "CUST0489",
431
+ "CUST0499"
432
+ ],
433
+ "size": 39,
434
+ "strong_link_count": 1,
435
+ "weak_link_count": 95,
436
+ "formation_type": "weak_only",
437
+ "method": "louvain"
438
+ },
439
+ {
440
+ "cluster_id": "CAND18",
441
+ "customer_ids": [
442
+ "CUST0007",
443
+ "CUST0014",
444
+ "CUST0015",
445
+ "CUST0016",
446
+ "CUST0032",
447
+ "CUST0037",
448
+ "CUST0065",
449
+ "CUST0069",
450
+ "CUST0070",
451
+ "CUST0095",
452
+ "CUST0106",
453
+ "CUST0115",
454
+ "CUST0124",
455
+ "CUST0131",
456
+ "CUST0149",
457
+ "CUST0191",
458
+ "CUST0200",
459
+ "CUST0204",
460
+ "CUST0215",
461
+ "CUST0218",
462
+ "CUST0223",
463
+ "CUST0238",
464
+ "CUST0239",
465
+ "CUST0241",
466
+ "CUST0242",
467
+ "CUST0264",
468
+ "CUST0276",
469
+ "CUST0280",
470
+ "CUST0305",
471
+ "CUST0314",
472
+ "CUST0323",
473
+ "CUST0327",
474
+ "CUST0339",
475
+ "CUST0350",
476
+ "CUST0352",
477
+ "CUST0356",
478
+ "CUST0358",
479
+ "CUST0362",
480
+ "CUST0379",
481
+ "CUST0401",
482
+ "CUST0418",
483
+ "CUST0440",
484
+ "CUST0443",
485
+ "CUST0449",
486
+ "CUST0479"
487
+ ],
488
+ "size": 45,
489
+ "strong_link_count": 3,
490
+ "weak_link_count": 106,
491
+ "formation_type": "weak_only",
492
+ "method": "louvain"
493
+ },
494
+ {
495
+ "cluster_id": "CAND19",
496
+ "customer_ids": [
497
+ "CUST0010",
498
+ "CUST0023",
499
+ "CUST0029",
500
+ "CUST0090",
501
+ "CUST0119",
502
+ "CUST0121",
503
+ "CUST0129",
504
+ "CUST0141",
505
+ "CUST0143",
506
+ "CUST0155",
507
+ "CUST0196",
508
+ "CUST0207",
509
+ "CUST0230",
510
+ "CUST0233",
511
+ "CUST0247",
512
+ "CUST0330",
513
+ "CUST0338",
514
+ "CUST0372",
515
+ "CUST0374",
516
+ "CUST0380",
517
+ "CUST0461",
518
+ "CUST0467",
519
+ "CUST0480",
520
+ "CUST0493"
521
+ ],
522
+ "size": 24,
523
+ "strong_link_count": 0,
524
+ "weak_link_count": 47,
525
+ "formation_type": "weak_only",
526
+ "method": "louvain"
527
+ },
528
+ {
529
+ "cluster_id": "CAND20",
530
+ "customer_ids": [
531
+ "CUST0013",
532
+ "CUST0026",
533
+ "CUST0031",
534
+ "CUST0035",
535
+ "CUST0039",
536
+ "CUST0067",
537
+ "CUST0068",
538
+ "CUST0094",
539
+ "CUST0116",
540
+ "CUST0122",
541
+ "CUST0128",
542
+ "CUST0184",
543
+ "CUST0193",
544
+ "CUST0203",
545
+ "CUST0212",
546
+ "CUST0217",
547
+ "CUST0248",
548
+ "CUST0268",
549
+ "CUST0283",
550
+ "CUST0312",
551
+ "CUST0340",
552
+ "CUST0351",
553
+ "CUST0384",
554
+ "CUST0402",
555
+ "CUST0417",
556
+ "CUST0427",
557
+ "CUST0430",
558
+ "CUST0434",
559
+ "CUST0451",
560
+ "CUST0463",
561
+ "CUST0466",
562
+ "CUST0469",
563
+ "CUST0473",
564
+ "CUST0482",
565
+ "CUST0497"
566
+ ],
567
+ "size": 35,
568
+ "strong_link_count": 0,
569
+ "weak_link_count": 84,
570
+ "formation_type": "weak_only",
571
+ "method": "louvain"
572
+ },
573
+ {
574
+ "cluster_id": "CAND21",
575
+ "customer_ids": [
576
+ "CUST0017",
577
+ "CUST0036",
578
+ "CUST0062",
579
+ "CUST0071",
580
+ "CUST0082",
581
+ "CUST0083",
582
+ "CUST0085",
583
+ "CUST0096",
584
+ "CUST0123",
585
+ "CUST0142",
586
+ "CUST0147",
587
+ "CUST0175",
588
+ "CUST0189",
589
+ "CUST0216",
590
+ "CUST0220",
591
+ "CUST0224",
592
+ "CUST0232",
593
+ "CUST0234",
594
+ "CUST0306",
595
+ "CUST0328",
596
+ "CUST0337",
597
+ "CUST0341",
598
+ "CUST0345",
599
+ "CUST0346",
600
+ "CUST0361",
601
+ "CUST0388",
602
+ "CUST0421",
603
+ "CUST0426",
604
+ "CUST0433",
605
+ "CUST0435",
606
+ "CUST0444",
607
+ "CUST0450",
608
+ "CUST0454",
609
+ "CUST0477"
610
+ ],
611
+ "size": 34,
612
+ "strong_link_count": 0,
613
+ "weak_link_count": 79,
614
+ "formation_type": "weak_only",
615
+ "method": "louvain"
616
+ },
617
+ {
618
+ "cluster_id": "CAND22",
619
+ "customer_ids": [
620
+ "CUST0018",
621
+ "CUST0021",
622
+ "CUST0091",
623
+ "CUST0092",
624
+ "CUST0112",
625
+ "CUST0160",
626
+ "CUST0174",
627
+ "CUST0187",
628
+ "CUST0270",
629
+ "CUST0298",
630
+ "CUST0307",
631
+ "CUST0310",
632
+ "CUST0332",
633
+ "CUST0343",
634
+ "CUST0431",
635
+ "CUST0474",
636
+ "CUST0475",
637
+ "CUST0494"
638
+ ],
639
+ "size": 18,
640
+ "strong_link_count": 0,
641
+ "weak_link_count": 35,
642
+ "formation_type": "weak_only",
643
+ "method": "louvain"
644
+ },
645
+ {
646
+ "cluster_id": "CAND23",
647
+ "customer_ids": [
648
+ "CUST0034",
649
+ "CUST0066",
650
+ "CUST0078",
651
+ "CUST0100",
652
+ "CUST0104",
653
+ "CUST0134",
654
+ "CUST0146",
655
+ "CUST0150",
656
+ "CUST0151",
657
+ "CUST0158",
658
+ "CUST0182",
659
+ "CUST0183",
660
+ "CUST0185",
661
+ "CUST0188",
662
+ "CUST0190",
663
+ "CUST0259",
664
+ "CUST0269",
665
+ "CUST0279",
666
+ "CUST0285",
667
+ "CUST0289",
668
+ "CUST0301",
669
+ "CUST0302",
670
+ "CUST0344",
671
+ "CUST0354",
672
+ "CUST0366",
673
+ "CUST0368",
674
+ "CUST0378",
675
+ "CUST0404",
676
+ "CUST0408",
677
+ "CUST0432",
678
+ "CUST0436",
679
+ "CUST0437",
680
+ "CUST0439",
681
+ "CUST0441",
682
+ "CUST0447",
683
+ "CUST0448",
684
+ "CUST0457",
685
+ "CUST0481"
686
+ ],
687
+ "size": 38,
688
+ "strong_link_count": 0,
689
+ "weak_link_count": 90,
690
+ "formation_type": "weak_only",
691
+ "method": "louvain"
692
+ },
693
+ {
694
+ "cluster_id": "CAND24",
695
+ "customer_ids": [
696
+ "CUST0061",
697
+ "CUST0093",
698
+ "CUST0105",
699
+ "CUST0130",
700
+ "CUST0136",
701
+ "CUST0148",
702
+ "CUST0164",
703
+ "CUST0186",
704
+ "CUST0197",
705
+ "CUST0205",
706
+ "CUST0206",
707
+ "CUST0214",
708
+ "CUST0260",
709
+ "CUST0309",
710
+ "CUST0320",
711
+ "CUST0321",
712
+ "CUST0359",
713
+ "CUST0363",
714
+ "CUST0376",
715
+ "CUST0377",
716
+ "CUST0385",
717
+ "CUST0394",
718
+ "CUST0403",
719
+ "CUST0416",
720
+ "CUST0424",
721
+ "CUST0453"
722
+ ],
723
+ "size": 26,
724
+ "strong_link_count": 0,
725
+ "weak_link_count": 71,
726
+ "formation_type": "weak_only",
727
+ "method": "louvain"
728
+ },
729
+ {
730
+ "cluster_id": "CAND25",
731
+ "customer_ids": [
732
+ "CUST0045",
733
+ "CUST0046",
734
+ "CUST0047",
735
+ "CUST0048"
736
+ ],
737
+ "size": 4,
738
+ "strong_link_count": 0,
739
+ "weak_link_count": 6,
740
+ "formation_type": "weak_only",
741
+ "method": "connected_components"
742
+ },
743
+ {
744
+ "cluster_id": "CAND26",
745
+ "customer_ids": [
746
+ "CUST0049",
747
+ "CUST0050",
748
+ "CUST0051",
749
+ "CUST0052"
750
+ ],
751
+ "size": 4,
752
+ "strong_link_count": 0,
753
+ "weak_link_count": 6,
754
+ "formation_type": "weak_only",
755
+ "method": "connected_components"
756
+ },
757
+ {
758
+ "cluster_id": "CAND27",
759
+ "customer_ids": [
760
+ "CUST0053",
761
+ "CUST0054",
762
+ "CUST0055",
763
+ "CUST0056"
764
+ ],
765
+ "size": 4,
766
+ "strong_link_count": 0,
767
+ "weak_link_count": 6,
768
+ "formation_type": "weak_only",
769
+ "method": "connected_components"
770
+ },
771
+ {
772
+ "cluster_id": "CAND28",
773
+ "customer_ids": [
774
+ "CUST0504",
775
+ "CUST0505",
776
+ "CUST0506"
777
+ ],
778
+ "size": 3,
779
+ "strong_link_count": 0,
780
+ "weak_link_count": 3,
781
+ "formation_type": "weak_only",
782
+ "method": "connected_components"
783
+ }
784
+ ]
data/synthetic/cases_demo.json ADDED
@@ -0,0 +1,1027 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "case_id": "CASE01",
4
+ "cluster_id": "CAND01",
5
+ "customer_ids": [
6
+ "CUST0041",
7
+ "CUST0042",
8
+ "CUST0043",
9
+ "CUST0044"
10
+ ],
11
+ "risk_probability": 0.9958773824170732,
12
+ "policy_decision": "escalate",
13
+ "llm_narrative": "The four customers (CUST0041, CUST0042, CUST0043, CUST0044) form a strong‑anchored cluster with six strong links and no weak links, indicating tightly coordinated activity. Their combined purchase total of $58,186 is accompanied by a 33.3% return rate and a 16.7% chargeback rate, further supporting fraudulent behavior.",
14
+ "llm_confidence_statement": "The model assigns a risk probability of 99.6%, indicating very high confidence in the escalation decision.",
15
+ "action_taken": {
16
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
17
+ "order_id": "order_TSeidafHmbAnDR",
18
+ "razorpay_status": "created",
19
+ "notes_written": {
20
+ "case_id": "CASE01",
21
+ "cluster_id": "CAND01",
22
+ "ringguard_status": "escalate_flagged",
23
+ "action_label": "FRAUD_RING_ESCALATE",
24
+ "risk_score": "0.9959",
25
+ "action_by": "RingGuard_v3",
26
+ "action_timestamp": "2026-08-22T02:47:10.102605+00:00"
27
+ },
28
+ "notes_verified": true,
29
+ "timestamp": "2026-08-22T02:47:10.941027+00:00",
30
+ "test_mode": true,
31
+ "error": null
32
+ },
33
+ "timestamp": "2026-08-22T02:42:02.088359+00:00"
34
+ },
35
+ {
36
+ "case_id": "CASE02",
37
+ "cluster_id": "CAND02",
38
+ "customer_ids": [
39
+ "CUST0009",
40
+ "CUST0010",
41
+ "CUST0011",
42
+ "CUST0012",
43
+ "CUST0013"
44
+ ],
45
+ "risk_probability": 0.9968053865328341,
46
+ "policy_decision": "escalate",
47
+ "llm_narrative": "The five customers (CUST0009‑CUST0013) form a strong‑anchored cluster with ten strong links and no weak links. The group shows a return rate of 47% and a chargeback rate of 18%, with total transaction volume of $59,830. The risk model assigns a probability of 99.7%, resulting in an escalation decision.",
48
+ "llm_confidence_statement": "We are highly confident in this assessment given the very high risk probability and supporting metrics.",
49
+ "action_taken": {
50
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
51
+ "order_id": "order_TSeieevTUEtDNP",
52
+ "razorpay_status": "created",
53
+ "notes_written": {
54
+ "case_id": "CASE02",
55
+ "cluster_id": "CAND02",
56
+ "ringguard_status": "escalate_flagged",
57
+ "action_label": "FRAUD_RING_ESCALATE",
58
+ "risk_score": "0.9968",
59
+ "action_by": "RingGuard_v3",
60
+ "action_timestamp": "2026-08-22T02:47:11.088678+00:00"
61
+ },
62
+ "notes_verified": true,
63
+ "timestamp": "2026-08-22T02:47:11.992374+00:00",
64
+ "test_mode": true,
65
+ "error": null
66
+ },
67
+ "timestamp": "2026-08-22T02:42:06.481196+00:00"
68
+ },
69
+ {
70
+ "case_id": "CASE03",
71
+ "cluster_id": "CAND03",
72
+ "customer_ids": [
73
+ "CUST0001",
74
+ "CUST0002",
75
+ "CUST0003"
76
+ ],
77
+ "risk_probability": 0.9950028638127228,
78
+ "policy_decision": "escalate",
79
+ "llm_narrative": "The three customers (CUST0001, CUST0002, CUST0003) are connected by three strong links, forming a strong‑anchored cluster. This cluster accounts for a total transaction amount of 32,897, includes one shared coupon, and exhibits a 25% return rate and a 12.5% chargeback rate, resulting in a risk probability of 99.5%.",
80
+ "llm_confidence_statement": "The model’s confidence in this risk assessment is extremely high.",
81
+ "action_taken": {
82
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
83
+ "order_id": "order_TSeifof2nHqiyN",
84
+ "razorpay_status": "created",
85
+ "notes_written": {
86
+ "case_id": "CASE03",
87
+ "cluster_id": "CAND03",
88
+ "ringguard_status": "escalate_flagged",
89
+ "action_label": "FRAUD_RING_ESCALATE",
90
+ "risk_score": "0.995",
91
+ "action_by": "RingGuard_v3",
92
+ "action_timestamp": "2026-08-22T02:47:12.186378+00:00"
93
+ },
94
+ "notes_verified": true,
95
+ "timestamp": "2026-08-22T02:47:13.232792+00:00",
96
+ "test_mode": true,
97
+ "error": null
98
+ },
99
+ "timestamp": "2026-08-22T02:42:10.844934+00:00"
100
+ },
101
+ {
102
+ "case_id": "CASE04",
103
+ "cluster_id": "CAND04",
104
+ "customer_ids": [
105
+ "CUST0014",
106
+ "CUST0015",
107
+ "CUST0016",
108
+ "CUST0017",
109
+ "CUST0018"
110
+ ],
111
+ "risk_probability": 0.9955003588432927,
112
+ "policy_decision": "escalate",
113
+ "llm_narrative": "The five customers (CUST0014‑CUST0018) form a strong‑anchored cluster with ten strong links and no weak links, collectively spending $62,087. The group exhibits a 50% return rate, a 12.5% chargeback rate, and shares a single coupon, indicating coordinated risky behavior.",
114
+ "llm_confidence_statement": "Given the 0.9955 risk probability, we are highly confident that this cluster represents a high‑risk fraud pattern.",
115
+ "action_taken": {
116
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
117
+ "order_id": "order_TSeihAqIgYfEsF",
118
+ "razorpay_status": "created",
119
+ "notes_written": {
120
+ "case_id": "CASE04",
121
+ "cluster_id": "CAND04",
122
+ "ringguard_status": "escalate_flagged",
123
+ "action_label": "FRAUD_RING_ESCALATE",
124
+ "risk_score": "0.9955",
125
+ "action_by": "RingGuard_v3",
126
+ "action_timestamp": "2026-08-22T02:47:13.364816+00:00"
127
+ },
128
+ "notes_verified": true,
129
+ "timestamp": "2026-08-22T02:47:14.280655+00:00",
130
+ "test_mode": true,
131
+ "error": null
132
+ },
133
+ "timestamp": "2026-08-22T02:42:15.156355+00:00"
134
+ },
135
+ {
136
+ "case_id": "CASE05",
137
+ "cluster_id": "CAND05",
138
+ "customer_ids": [
139
+ "CUST0033",
140
+ "CUST0034",
141
+ "CUST0035"
142
+ ],
143
+ "risk_probability": 0.9969893801631614,
144
+ "policy_decision": "escalate",
145
+ "llm_narrative": "The three customers (CUST0033, CUST0034, CUST0035) are connected by three strong links, forming a strong‑anchored cluster with a shared coupon and a combined transaction volume of $50,134. The cluster exhibits a return rate of 18.2% and a chargeback rate of 9.1%, and its calculated risk probability is 99.7%.",
146
+ "llm_confidence_statement": "We are highly confident in the fraud assessment given the strong linkage, high risk probability, and notable financial metrics.",
147
+ "action_taken": {
148
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
149
+ "order_id": "order_TSeiiK16ZPVegj",
150
+ "razorpay_status": "created",
151
+ "notes_written": {
152
+ "case_id": "CASE05",
153
+ "cluster_id": "CAND05",
154
+ "ringguard_status": "escalate_flagged",
155
+ "action_label": "FRAUD_RING_ESCALATE",
156
+ "risk_score": "0.997",
157
+ "action_by": "RingGuard_v3",
158
+ "action_timestamp": "2026-08-22T02:47:14.523378+00:00"
159
+ },
160
+ "notes_verified": true,
161
+ "timestamp": "2026-08-22T02:47:15.299833+00:00",
162
+ "test_mode": true,
163
+ "error": null
164
+ },
165
+ "timestamp": "2026-08-22T02:42:19.568153+00:00"
166
+ },
167
+ {
168
+ "case_id": "CASE06",
169
+ "cluster_id": "CAND06",
170
+ "customer_ids": [
171
+ "CUST0036",
172
+ "CUST0037",
173
+ "CUST0038",
174
+ "CUST0039",
175
+ "CUST0040"
176
+ ],
177
+ "risk_probability": 0.9969377389203431,
178
+ "policy_decision": "escalate",
179
+ "llm_narrative": "The five customers (CUST0036‑CUST0040) form a strong‑anchored cluster with ten strong links and no weak links, collectively transacting $62,270. Their return rate of 26.7% and chargeback rate of 20% are elevated, and the absence of shared coupons suggests the links are not promotional. These characteristics indicate coordinated fraudulent behavior.",
180
+ "llm_confidence_statement": "We are highly confident in this assessment, reflected by a risk probability of 0.997.",
181
+ "action_taken": {
182
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
183
+ "order_id": "order_TSeijQj96lsR88",
184
+ "razorpay_status": "created",
185
+ "notes_written": {
186
+ "case_id": "CASE06",
187
+ "cluster_id": "CAND06",
188
+ "ringguard_status": "escalate_flagged",
189
+ "action_label": "FRAUD_RING_ESCALATE",
190
+ "risk_score": "0.9969",
191
+ "action_by": "RingGuard_v3",
192
+ "action_timestamp": "2026-08-22T02:47:15.427579+00:00"
193
+ },
194
+ "notes_verified": true,
195
+ "timestamp": "2026-08-22T02:47:16.209387+00:00",
196
+ "test_mode": true,
197
+ "error": null
198
+ },
199
+ "timestamp": "2026-08-22T02:42:23.867363+00:00"
200
+ },
201
+ {
202
+ "case_id": "CASE07",
203
+ "cluster_id": "CAND07",
204
+ "customer_ids": [
205
+ "CUST0019",
206
+ "CUST0020",
207
+ "CUST0021",
208
+ "CUST0022",
209
+ "CUST0023"
210
+ ],
211
+ "risk_probability": 0.9940850384979173,
212
+ "policy_decision": "escalate",
213
+ "llm_narrative": "A cluster of five customers (CUST0019‑CUST0023) is connected by ten strong links with no weak links, forming a strong‑anchored network. The group has generated a total transaction amount of 70,841, a return rate of 33.33% and a chargeback rate of 5.56%, and the model assigns a risk probability of 99.41%.",
214
+ "llm_confidence_statement": "The assessment is made with very high confidence.",
215
+ "action_taken": {
216
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
217
+ "order_id": "order_TSeikSedcIcNGX",
218
+ "razorpay_status": "created",
219
+ "notes_written": {
220
+ "case_id": "CASE07",
221
+ "cluster_id": "CAND07",
222
+ "ringguard_status": "escalate_flagged",
223
+ "action_label": "FRAUD_RING_ESCALATE",
224
+ "risk_score": "0.9941",
225
+ "action_by": "RingGuard_v3",
226
+ "action_timestamp": "2026-08-22T02:47:16.396113+00:00"
227
+ },
228
+ "notes_verified": true,
229
+ "timestamp": "2026-08-22T02:47:17.279431+00:00",
230
+ "test_mode": true,
231
+ "error": null
232
+ },
233
+ "timestamp": "2026-08-22T02:42:28.164967+00:00"
234
+ },
235
+ {
236
+ "case_id": "CASE08",
237
+ "cluster_id": "CAND08",
238
+ "customer_ids": [
239
+ "CUST0501",
240
+ "CUST0502",
241
+ "CUST0503"
242
+ ],
243
+ "risk_probability": 0.9867466290228035,
244
+ "policy_decision": "auto_hold",
245
+ "llm_narrative": "The three customers (CUST0501, CUST0502, CUST0503) are connected by three strong links, forming a strong‑anchored cluster. They share one coupon, together generated $19,800 in transactions, and exhibit a 16.7% return and chargeback rate each.",
246
+ "llm_confidence_statement": "The model assigns a risk probability of 0.99, indicating very high confidence in the high‑risk classification.",
247
+ "action_taken": {
248
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
249
+ "order_id": "order_TSeilZzpguMme6",
250
+ "razorpay_status": "created",
251
+ "notes_written": {
252
+ "case_id": "CASE08",
253
+ "cluster_id": "CAND08",
254
+ "ringguard_status": "auto_hold",
255
+ "action_label": "FRAUD_RING_AUTO_HOLD",
256
+ "risk_score": "0.9867",
257
+ "action_by": "RingGuard_v3",
258
+ "action_timestamp": "2026-08-22T02:47:17.462450+00:00"
259
+ },
260
+ "notes_verified": true,
261
+ "timestamp": "2026-08-22T02:47:18.321891+00:00",
262
+ "test_mode": true,
263
+ "error": null
264
+ },
265
+ "timestamp": "2026-08-22T02:42:32.454693+00:00"
266
+ },
267
+ {
268
+ "case_id": "CASE09",
269
+ "cluster_id": "CAND09",
270
+ "customer_ids": [
271
+ "CUST0024",
272
+ "CUST0025",
273
+ "CUST0026",
274
+ "CUST0027",
275
+ "CUST0028",
276
+ "CUST0029"
277
+ ],
278
+ "risk_probability": 0.9913203009684713,
279
+ "policy_decision": "escalate",
280
+ "llm_narrative": "The six customers (CUST0024‑CUST0029) form a strong_anchored cluster with 15 strong links and no weak links, indicating tightly coordinated activity. Their combined purchase total of 81,019 and a return rate of 55% further highlight abnormal behavior, while a chargeback rate of 5% adds to the risk profile.",
281
+ "llm_confidence_statement": "The evidence yields a very high confidence in the elevated risk assessment.",
282
+ "action_taken": {
283
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
284
+ "order_id": "order_TSeimiDR56y6I3",
285
+ "razorpay_status": "created",
286
+ "notes_written": {
287
+ "case_id": "CASE09",
288
+ "cluster_id": "CAND09",
289
+ "ringguard_status": "escalate_flagged",
290
+ "action_label": "FRAUD_RING_ESCALATE",
291
+ "risk_score": "0.9913",
292
+ "action_by": "RingGuard_v3",
293
+ "action_timestamp": "2026-08-22T02:47:18.435548+00:00"
294
+ },
295
+ "notes_verified": true,
296
+ "timestamp": "2026-08-22T02:47:19.433491+00:00",
297
+ "test_mode": true,
298
+ "error": null
299
+ },
300
+ "timestamp": "2026-08-22T02:42:36.567254+00:00"
301
+ },
302
+ {
303
+ "case_id": "CASE10",
304
+ "cluster_id": "CAND10",
305
+ "customer_ids": [
306
+ "CUST0004",
307
+ "CUST0005",
308
+ "CUST0006",
309
+ "CUST0007",
310
+ "CUST0008"
311
+ ],
312
+ "risk_probability": 0.9959231873282086,
313
+ "policy_decision": "escalate",
314
+ "llm_narrative": "The five customers (CUST0004‑CUST0008) form a strong‑anchored cluster with seven strong links and no weak links, collectively transacting $63,837. Their return rate of 37.5% and chargeback rate of 6.25% are elevated, supporting the high risk probability of 99.6%.",
315
+ "llm_confidence_statement": "The evidence yields a very high confidence in the elevated fraud risk assessment.",
316
+ "action_taken": {
317
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
318
+ "order_id": "order_TSeinwRO4xKvs9",
319
+ "razorpay_status": "created",
320
+ "notes_written": {
321
+ "case_id": "CASE10",
322
+ "cluster_id": "CAND10",
323
+ "ringguard_status": "escalate_flagged",
324
+ "action_label": "FRAUD_RING_ESCALATE",
325
+ "risk_score": "0.9959",
326
+ "action_by": "RingGuard_v3",
327
+ "action_timestamp": "2026-08-22T02:47:19.567020+00:00"
328
+ },
329
+ "notes_verified": true,
330
+ "timestamp": "2026-08-22T02:47:20.455513+00:00",
331
+ "test_mode": true,
332
+ "error": null
333
+ },
334
+ "timestamp": "2026-08-22T02:42:40.796349+00:00"
335
+ },
336
+ {
337
+ "case_id": "CASE11",
338
+ "cluster_id": "CAND11",
339
+ "customer_ids": [
340
+ "CUST0030",
341
+ "CUST0031",
342
+ "CUST0032"
343
+ ],
344
+ "risk_probability": 0.9927212653417073,
345
+ "policy_decision": "escalate",
346
+ "llm_narrative": "The three customers (CUST0030, CUST0031, CUST0032) form a strong‑anchored cluster with two strong links and no weak links, together generating $26,235 in transactions. Their return and chargeback rates are each 28.6%, and the model assigns a risk probability of 99.3%.",
347
+ "llm_confidence_statement": "Confidence in this assessment is high given the strong links, elevated return/chargeback rates, and near‑certain risk probability.",
348
+ "action_taken": {
349
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
350
+ "order_id": "order_TSeip2lX554G6F",
351
+ "razorpay_status": "created",
352
+ "notes_written": {
353
+ "case_id": "CASE11",
354
+ "cluster_id": "CAND11",
355
+ "ringguard_status": "escalate_flagged",
356
+ "action_label": "FRAUD_RING_ESCALATE",
357
+ "risk_score": "0.9927",
358
+ "action_by": "RingGuard_v3",
359
+ "action_timestamp": "2026-08-22T02:47:20.576284+00:00"
360
+ },
361
+ "notes_verified": true,
362
+ "timestamp": "2026-08-22T02:47:21.336242+00:00",
363
+ "test_mode": true,
364
+ "error": null
365
+ },
366
+ "timestamp": "2026-08-22T02:42:46.478223+00:00"
367
+ },
368
+ {
369
+ "case_id": "CASE12",
370
+ "cluster_id": "CAND12",
371
+ "customer_ids": [
372
+ "CUST0001",
373
+ "CUST0019",
374
+ "CUST0040",
375
+ "CUST0126",
376
+ "CUST0138",
377
+ "CUST0168",
378
+ "CUST0211",
379
+ "CUST0219",
380
+ "CUST0258",
381
+ "CUST0281",
382
+ "CUST0292",
383
+ "CUST0322",
384
+ "CUST0325",
385
+ "CUST0371",
386
+ "CUST0373",
387
+ "CUST0400",
388
+ "CUST0419",
389
+ "CUST0465"
390
+ ],
391
+ "risk_probability": 0.00720344453548541,
392
+ "policy_decision": "log_only",
393
+ "llm_narrative": null,
394
+ "llm_confidence_statement": null,
395
+ "action_taken": null,
396
+ "timestamp": "2026-08-22T02:42:46.483242+00:00"
397
+ },
398
+ {
399
+ "case_id": "CASE13",
400
+ "cluster_id": "CAND13",
401
+ "customer_ids": [
402
+ "CUST0002",
403
+ "CUST0009",
404
+ "CUST0020",
405
+ "CUST0041",
406
+ "CUST0064",
407
+ "CUST0073",
408
+ "CUST0074",
409
+ "CUST0080",
410
+ "CUST0081",
411
+ "CUST0086",
412
+ "CUST0089",
413
+ "CUST0103",
414
+ "CUST0162",
415
+ "CUST0167",
416
+ "CUST0173",
417
+ "CUST0181",
418
+ "CUST0226",
419
+ "CUST0240",
420
+ "CUST0253",
421
+ "CUST0262",
422
+ "CUST0272",
423
+ "CUST0286",
424
+ "CUST0287",
425
+ "CUST0324",
426
+ "CUST0331",
427
+ "CUST0333",
428
+ "CUST0369",
429
+ "CUST0392",
430
+ "CUST0393",
431
+ "CUST0398",
432
+ "CUST0406",
433
+ "CUST0410",
434
+ "CUST0411",
435
+ "CUST0413",
436
+ "CUST0415",
437
+ "CUST0423",
438
+ "CUST0446",
439
+ "CUST0468",
440
+ "CUST0476",
441
+ "CUST0478",
442
+ "CUST0485"
443
+ ],
444
+ "risk_probability": 0.007559966037372132,
445
+ "policy_decision": "log_only",
446
+ "llm_narrative": null,
447
+ "llm_confidence_statement": null,
448
+ "action_taken": null,
449
+ "timestamp": "2026-08-22T02:42:46.486423+00:00"
450
+ },
451
+ {
452
+ "case_id": "CASE14",
453
+ "cluster_id": "CAND14",
454
+ "customer_ids": [
455
+ "CUST0003",
456
+ "CUST0012",
457
+ "CUST0043",
458
+ "CUST0077",
459
+ "CUST0088",
460
+ "CUST0098",
461
+ "CUST0109",
462
+ "CUST0125",
463
+ "CUST0139",
464
+ "CUST0153",
465
+ "CUST0159",
466
+ "CUST0166",
467
+ "CUST0180",
468
+ "CUST0201",
469
+ "CUST0209",
470
+ "CUST0245",
471
+ "CUST0261",
472
+ "CUST0265",
473
+ "CUST0273",
474
+ "CUST0277",
475
+ "CUST0297",
476
+ "CUST0315",
477
+ "CUST0334",
478
+ "CUST0347",
479
+ "CUST0381",
480
+ "CUST0405",
481
+ "CUST0412",
482
+ "CUST0428",
483
+ "CUST0429",
484
+ "CUST0462",
485
+ "CUST0471"
486
+ ],
487
+ "risk_probability": 0.0033586500336385537,
488
+ "policy_decision": "log_only",
489
+ "llm_narrative": null,
490
+ "llm_confidence_statement": null,
491
+ "action_taken": null,
492
+ "timestamp": "2026-08-22T02:42:46.490015+00:00"
493
+ },
494
+ {
495
+ "case_id": "CASE15",
496
+ "cluster_id": "CAND15",
497
+ "customer_ids": [
498
+ "CUST0004",
499
+ "CUST0008",
500
+ "CUST0027",
501
+ "CUST0030",
502
+ "CUST0042",
503
+ "CUST0063",
504
+ "CUST0075",
505
+ "CUST0087",
506
+ "CUST0110",
507
+ "CUST0117",
508
+ "CUST0120",
509
+ "CUST0133",
510
+ "CUST0135",
511
+ "CUST0137",
512
+ "CUST0144",
513
+ "CUST0152",
514
+ "CUST0154",
515
+ "CUST0163",
516
+ "CUST0170",
517
+ "CUST0178",
518
+ "CUST0179",
519
+ "CUST0195",
520
+ "CUST0198",
521
+ "CUST0208",
522
+ "CUST0210",
523
+ "CUST0221",
524
+ "CUST0228",
525
+ "CUST0231",
526
+ "CUST0237",
527
+ "CUST0246",
528
+ "CUST0251",
529
+ "CUST0254",
530
+ "CUST0266",
531
+ "CUST0282",
532
+ "CUST0295",
533
+ "CUST0300",
534
+ "CUST0303",
535
+ "CUST0304",
536
+ "CUST0335",
537
+ "CUST0355",
538
+ "CUST0365",
539
+ "CUST0399",
540
+ "CUST0420",
541
+ "CUST0425",
542
+ "CUST0470",
543
+ "CUST0483",
544
+ "CUST0486",
545
+ "CUST0487",
546
+ "CUST0491",
547
+ "CUST0492",
548
+ "CUST0498"
549
+ ],
550
+ "risk_probability": 0.007646314104742993,
551
+ "policy_decision": "log_only",
552
+ "llm_narrative": null,
553
+ "llm_confidence_statement": null,
554
+ "action_taken": null,
555
+ "timestamp": "2026-08-22T02:42:46.491127+00:00"
556
+ },
557
+ {
558
+ "case_id": "CASE16",
559
+ "cluster_id": "CAND16",
560
+ "customer_ids": [
561
+ "CUST0005",
562
+ "CUST0022",
563
+ "CUST0028",
564
+ "CUST0038",
565
+ "CUST0102",
566
+ "CUST0111",
567
+ "CUST0113",
568
+ "CUST0140",
569
+ "CUST0161",
570
+ "CUST0171",
571
+ "CUST0177",
572
+ "CUST0202",
573
+ "CUST0222",
574
+ "CUST0235",
575
+ "CUST0252",
576
+ "CUST0257",
577
+ "CUST0263",
578
+ "CUST0288",
579
+ "CUST0293",
580
+ "CUST0299",
581
+ "CUST0308",
582
+ "CUST0311",
583
+ "CUST0353",
584
+ "CUST0357",
585
+ "CUST0364",
586
+ "CUST0370",
587
+ "CUST0383",
588
+ "CUST0386",
589
+ "CUST0387",
590
+ "CUST0391",
591
+ "CUST0396",
592
+ "CUST0438",
593
+ "CUST0442",
594
+ "CUST0456",
595
+ "CUST0460",
596
+ "CUST0488",
597
+ "CUST0490",
598
+ "CUST0495",
599
+ "CUST0496",
600
+ "CUST0500"
601
+ ],
602
+ "risk_probability": 0.0035456513474297945,
603
+ "policy_decision": "log_only",
604
+ "llm_narrative": null,
605
+ "llm_confidence_statement": null,
606
+ "action_taken": null,
607
+ "timestamp": "2026-08-22T02:42:46.495874+00:00"
608
+ },
609
+ {
610
+ "case_id": "CASE17",
611
+ "cluster_id": "CAND17",
612
+ "customer_ids": [
613
+ "CUST0006",
614
+ "CUST0011",
615
+ "CUST0024",
616
+ "CUST0025",
617
+ "CUST0033",
618
+ "CUST0044",
619
+ "CUST0079",
620
+ "CUST0084",
621
+ "CUST0101",
622
+ "CUST0107",
623
+ "CUST0108",
624
+ "CUST0114",
625
+ "CUST0132",
626
+ "CUST0145",
627
+ "CUST0157",
628
+ "CUST0172",
629
+ "CUST0227",
630
+ "CUST0244",
631
+ "CUST0255",
632
+ "CUST0256",
633
+ "CUST0267",
634
+ "CUST0271",
635
+ "CUST0284",
636
+ "CUST0291",
637
+ "CUST0296",
638
+ "CUST0316",
639
+ "CUST0319",
640
+ "CUST0329",
641
+ "CUST0336",
642
+ "CUST0342",
643
+ "CUST0348",
644
+ "CUST0349",
645
+ "CUST0367",
646
+ "CUST0375",
647
+ "CUST0389",
648
+ "CUST0407",
649
+ "CUST0455",
650
+ "CUST0489",
651
+ "CUST0499"
652
+ ],
653
+ "risk_probability": 0.006896318028648148,
654
+ "policy_decision": "log_only",
655
+ "llm_narrative": null,
656
+ "llm_confidence_statement": null,
657
+ "action_taken": null,
658
+ "timestamp": "2026-08-22T02:42:46.500167+00:00"
659
+ },
660
+ {
661
+ "case_id": "CASE18",
662
+ "cluster_id": "CAND18",
663
+ "customer_ids": [
664
+ "CUST0007",
665
+ "CUST0014",
666
+ "CUST0015",
667
+ "CUST0016",
668
+ "CUST0032",
669
+ "CUST0037",
670
+ "CUST0065",
671
+ "CUST0069",
672
+ "CUST0070",
673
+ "CUST0095",
674
+ "CUST0106",
675
+ "CUST0115",
676
+ "CUST0124",
677
+ "CUST0131",
678
+ "CUST0149",
679
+ "CUST0191",
680
+ "CUST0200",
681
+ "CUST0204",
682
+ "CUST0215",
683
+ "CUST0218",
684
+ "CUST0223",
685
+ "CUST0238",
686
+ "CUST0239",
687
+ "CUST0241",
688
+ "CUST0242",
689
+ "CUST0264",
690
+ "CUST0276",
691
+ "CUST0280",
692
+ "CUST0305",
693
+ "CUST0314",
694
+ "CUST0323",
695
+ "CUST0327",
696
+ "CUST0339",
697
+ "CUST0350",
698
+ "CUST0352",
699
+ "CUST0356",
700
+ "CUST0358",
701
+ "CUST0362",
702
+ "CUST0379",
703
+ "CUST0401",
704
+ "CUST0418",
705
+ "CUST0440",
706
+ "CUST0443",
707
+ "CUST0449",
708
+ "CUST0479"
709
+ ],
710
+ "risk_probability": 0.0034784417949967547,
711
+ "policy_decision": "log_only",
712
+ "llm_narrative": null,
713
+ "llm_confidence_statement": null,
714
+ "action_taken": null,
715
+ "timestamp": "2026-08-22T02:42:46.502500+00:00"
716
+ },
717
+ {
718
+ "case_id": "CASE19",
719
+ "cluster_id": "CAND19",
720
+ "customer_ids": [
721
+ "CUST0010",
722
+ "CUST0023",
723
+ "CUST0029",
724
+ "CUST0090",
725
+ "CUST0119",
726
+ "CUST0121",
727
+ "CUST0129",
728
+ "CUST0141",
729
+ "CUST0143",
730
+ "CUST0155",
731
+ "CUST0196",
732
+ "CUST0207",
733
+ "CUST0230",
734
+ "CUST0233",
735
+ "CUST0247",
736
+ "CUST0330",
737
+ "CUST0338",
738
+ "CUST0372",
739
+ "CUST0374",
740
+ "CUST0380",
741
+ "CUST0461",
742
+ "CUST0467",
743
+ "CUST0480",
744
+ "CUST0493"
745
+ ],
746
+ "risk_probability": 0.007559966037372132,
747
+ "policy_decision": "log_only",
748
+ "llm_narrative": null,
749
+ "llm_confidence_statement": null,
750
+ "action_taken": null,
751
+ "timestamp": "2026-08-22T02:42:46.505560+00:00"
752
+ },
753
+ {
754
+ "case_id": "CASE20",
755
+ "cluster_id": "CAND20",
756
+ "customer_ids": [
757
+ "CUST0013",
758
+ "CUST0026",
759
+ "CUST0031",
760
+ "CUST0035",
761
+ "CUST0039",
762
+ "CUST0067",
763
+ "CUST0068",
764
+ "CUST0094",
765
+ "CUST0116",
766
+ "CUST0122",
767
+ "CUST0128",
768
+ "CUST0184",
769
+ "CUST0193",
770
+ "CUST0203",
771
+ "CUST0212",
772
+ "CUST0217",
773
+ "CUST0248",
774
+ "CUST0268",
775
+ "CUST0283",
776
+ "CUST0312",
777
+ "CUST0340",
778
+ "CUST0351",
779
+ "CUST0384",
780
+ "CUST0402",
781
+ "CUST0417",
782
+ "CUST0427",
783
+ "CUST0430",
784
+ "CUST0434",
785
+ "CUST0451",
786
+ "CUST0463",
787
+ "CUST0466",
788
+ "CUST0469",
789
+ "CUST0473",
790
+ "CUST0482",
791
+ "CUST0497"
792
+ ],
793
+ "risk_probability": 0.007561351403107364,
794
+ "policy_decision": "log_only",
795
+ "llm_narrative": null,
796
+ "llm_confidence_statement": null,
797
+ "action_taken": null,
798
+ "timestamp": "2026-08-22T02:42:46.508965+00:00"
799
+ },
800
+ {
801
+ "case_id": "CASE21",
802
+ "cluster_id": "CAND21",
803
+ "customer_ids": [
804
+ "CUST0017",
805
+ "CUST0036",
806
+ "CUST0062",
807
+ "CUST0071",
808
+ "CUST0082",
809
+ "CUST0083",
810
+ "CUST0085",
811
+ "CUST0096",
812
+ "CUST0123",
813
+ "CUST0142",
814
+ "CUST0147",
815
+ "CUST0175",
816
+ "CUST0189",
817
+ "CUST0216",
818
+ "CUST0220",
819
+ "CUST0224",
820
+ "CUST0232",
821
+ "CUST0234",
822
+ "CUST0306",
823
+ "CUST0328",
824
+ "CUST0337",
825
+ "CUST0341",
826
+ "CUST0345",
827
+ "CUST0346",
828
+ "CUST0361",
829
+ "CUST0388",
830
+ "CUST0421",
831
+ "CUST0426",
832
+ "CUST0433",
833
+ "CUST0435",
834
+ "CUST0444",
835
+ "CUST0450",
836
+ "CUST0454",
837
+ "CUST0477"
838
+ ],
839
+ "risk_probability": 0.0035456513474297945,
840
+ "policy_decision": "log_only",
841
+ "llm_narrative": null,
842
+ "llm_confidence_statement": null,
843
+ "action_taken": null,
844
+ "timestamp": "2026-08-22T02:42:46.514247+00:00"
845
+ },
846
+ {
847
+ "case_id": "CASE22",
848
+ "cluster_id": "CAND22",
849
+ "customer_ids": [
850
+ "CUST0018",
851
+ "CUST0021",
852
+ "CUST0091",
853
+ "CUST0092",
854
+ "CUST0112",
855
+ "CUST0160",
856
+ "CUST0174",
857
+ "CUST0187",
858
+ "CUST0270",
859
+ "CUST0298",
860
+ "CUST0307",
861
+ "CUST0310",
862
+ "CUST0332",
863
+ "CUST0343",
864
+ "CUST0431",
865
+ "CUST0474",
866
+ "CUST0475",
867
+ "CUST0494"
868
+ ],
869
+ "risk_probability": 0.03305941612108949,
870
+ "policy_decision": "log_only",
871
+ "llm_narrative": null,
872
+ "llm_confidence_statement": null,
873
+ "action_taken": null,
874
+ "timestamp": "2026-08-22T02:42:46.516340+00:00"
875
+ },
876
+ {
877
+ "case_id": "CASE23",
878
+ "cluster_id": "CAND23",
879
+ "customer_ids": [
880
+ "CUST0034",
881
+ "CUST0066",
882
+ "CUST0078",
883
+ "CUST0100",
884
+ "CUST0104",
885
+ "CUST0134",
886
+ "CUST0146",
887
+ "CUST0150",
888
+ "CUST0151",
889
+ "CUST0158",
890
+ "CUST0182",
891
+ "CUST0183",
892
+ "CUST0185",
893
+ "CUST0188",
894
+ "CUST0190",
895
+ "CUST0259",
896
+ "CUST0269",
897
+ "CUST0279",
898
+ "CUST0285",
899
+ "CUST0289",
900
+ "CUST0301",
901
+ "CUST0302",
902
+ "CUST0344",
903
+ "CUST0354",
904
+ "CUST0366",
905
+ "CUST0368",
906
+ "CUST0378",
907
+ "CUST0404",
908
+ "CUST0408",
909
+ "CUST0432",
910
+ "CUST0436",
911
+ "CUST0437",
912
+ "CUST0439",
913
+ "CUST0441",
914
+ "CUST0447",
915
+ "CUST0448",
916
+ "CUST0457",
917
+ "CUST0481"
918
+ ],
919
+ "risk_probability": 0.0035456513474297945,
920
+ "policy_decision": "log_only",
921
+ "llm_narrative": null,
922
+ "llm_confidence_statement": null,
923
+ "action_taken": null,
924
+ "timestamp": "2026-08-22T02:42:46.520858+00:00"
925
+ },
926
+ {
927
+ "case_id": "CASE24",
928
+ "cluster_id": "CAND24",
929
+ "customer_ids": [
930
+ "CUST0061",
931
+ "CUST0093",
932
+ "CUST0105",
933
+ "CUST0130",
934
+ "CUST0136",
935
+ "CUST0148",
936
+ "CUST0164",
937
+ "CUST0186",
938
+ "CUST0197",
939
+ "CUST0205",
940
+ "CUST0206",
941
+ "CUST0214",
942
+ "CUST0260",
943
+ "CUST0309",
944
+ "CUST0320",
945
+ "CUST0321",
946
+ "CUST0359",
947
+ "CUST0363",
948
+ "CUST0376",
949
+ "CUST0377",
950
+ "CUST0385",
951
+ "CUST0394",
952
+ "CUST0403",
953
+ "CUST0416",
954
+ "CUST0424",
955
+ "CUST0453"
956
+ ],
957
+ "risk_probability": 0.0035456513474297945,
958
+ "policy_decision": "log_only",
959
+ "llm_narrative": null,
960
+ "llm_confidence_statement": null,
961
+ "action_taken": null,
962
+ "timestamp": "2026-08-22T02:42:46.523858+00:00"
963
+ },
964
+ {
965
+ "case_id": "CASE25",
966
+ "cluster_id": "CAND25",
967
+ "customer_ids": [
968
+ "CUST0045",
969
+ "CUST0046",
970
+ "CUST0047",
971
+ "CUST0048"
972
+ ],
973
+ "risk_probability": 0.0035484719416527992,
974
+ "policy_decision": "log_only",
975
+ "llm_narrative": null,
976
+ "llm_confidence_statement": null,
977
+ "action_taken": null,
978
+ "timestamp": "2026-08-22T02:42:46.526962+00:00"
979
+ },
980
+ {
981
+ "case_id": "CASE26",
982
+ "cluster_id": "CAND26",
983
+ "customer_ids": [
984
+ "CUST0049",
985
+ "CUST0050",
986
+ "CUST0051",
987
+ "CUST0052"
988
+ ],
989
+ "risk_probability": 0.0031715868230902835,
990
+ "policy_decision": "log_only",
991
+ "llm_narrative": null,
992
+ "llm_confidence_statement": null,
993
+ "action_taken": null,
994
+ "timestamp": "2026-08-22T02:42:46.530230+00:00"
995
+ },
996
+ {
997
+ "case_id": "CASE27",
998
+ "cluster_id": "CAND27",
999
+ "customer_ids": [
1000
+ "CUST0053",
1001
+ "CUST0054",
1002
+ "CUST0055",
1003
+ "CUST0056"
1004
+ ],
1005
+ "risk_probability": 0.003058997865465553,
1006
+ "policy_decision": "log_only",
1007
+ "llm_narrative": null,
1008
+ "llm_confidence_statement": null,
1009
+ "action_taken": null,
1010
+ "timestamp": "2026-08-22T02:42:46.534325+00:00"
1011
+ },
1012
+ {
1013
+ "case_id": "CASE28",
1014
+ "cluster_id": "CAND28",
1015
+ "customer_ids": [
1016
+ "CUST0504",
1017
+ "CUST0505",
1018
+ "CUST0506"
1019
+ ],
1020
+ "risk_probability": 0.003546939654202544,
1021
+ "policy_decision": "log_only",
1022
+ "llm_narrative": null,
1023
+ "llm_confidence_statement": null,
1024
+ "action_taken": null,
1025
+ "timestamp": "2026-08-22T02:42:46.537834+00:00"
1026
+ }
1027
+ ]
data/synthetic/customers.json ADDED
The diff for this file is too large to render. See raw diff
 
data/synthetic/devices.json ADDED
@@ -0,0 +1,1622 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "device_id": "DEV0001",
4
+ "fingerprint_hash": "80dd747f3716dd56b933c731983355211eea88582cb7cb97b1ee0602fe525d5b"
5
+ },
6
+ {
7
+ "device_id": "DEV0002",
8
+ "fingerprint_hash": "a2ca018cf73581bf404317a767e526092c45f55a7a6a2cc8a174a9151f3ce7dd"
9
+ },
10
+ {
11
+ "device_id": "DEV0003",
12
+ "fingerprint_hash": "c99d12ce6ca80227ac35a8c202fa708ddc95933c2c04e44e9eaf42792ce7bceb"
13
+ },
14
+ {
15
+ "device_id": "DEV0004",
16
+ "fingerprint_hash": "f927513b1a5a6e6e5814b13c49aca9ca4e1eac05e3cdbf5412a32a29cde57d69"
17
+ },
18
+ {
19
+ "device_id": "DEV0005",
20
+ "fingerprint_hash": "c0ea731f429a0dbfd7eb578e56342e57dd88a032a846ef4bab118927b7288cae"
21
+ },
22
+ {
23
+ "device_id": "DEV0006",
24
+ "fingerprint_hash": "85a6f257b8128daf5841388e36925a7188d80e60b603d5249c6f2c3384a06bf0"
25
+ },
26
+ {
27
+ "device_id": "DEV0007",
28
+ "fingerprint_hash": "3f7ad5dfdc47e2f1bdc56a302140cee0f22d497d7d522fb8866a8d01219fe156"
29
+ },
30
+ {
31
+ "device_id": "DEV0008",
32
+ "fingerprint_hash": "8b986911a10d39a2dfb0c5e5b8c576802a6df1ee945aee55e232926db3f513a1"
33
+ },
34
+ {
35
+ "device_id": "DEV0009",
36
+ "fingerprint_hash": "24cb30e25cd01403efb4b1b136d0f894cf1a418bc2eae55d085bb678b8a1c347"
37
+ },
38
+ {
39
+ "device_id": "DEV0010",
40
+ "fingerprint_hash": "de873c3e1a42235d7c544dd2e2f19a43c7f6fde42c7916780f8a89628c3e54dd"
41
+ },
42
+ {
43
+ "device_id": "DEV0011",
44
+ "fingerprint_hash": "82f5916c950f3834351b58856c880fe4094cf06ce20db6e7f4bf480c2835cf56"
45
+ },
46
+ {
47
+ "device_id": "DEV0012",
48
+ "fingerprint_hash": "e2f01e9e89b7a4ca0cecf625d781caec16e012be73bb28170b94db3ac5dcc11e"
49
+ },
50
+ {
51
+ "device_id": "DEV0013",
52
+ "fingerprint_hash": "92b0e7cc6b9af30e56ef6ebb34ecdd7ec2b28b810c4e7ddc196dff166339da14"
53
+ },
54
+ {
55
+ "device_id": "DEV0014",
56
+ "fingerprint_hash": "a28ac6a502fe2431f72e1f8f0b7d1a081279a5f12c98f2dde7cb6c747f1726e9"
57
+ },
58
+ {
59
+ "device_id": "DEV0015",
60
+ "fingerprint_hash": "8f745cdeba5ce6854a397da814afd57453b0bff7aa03f47554e8049b112c879a"
61
+ },
62
+ {
63
+ "device_id": "DEV0016",
64
+ "fingerprint_hash": "e55f152153a5de8d4b958763145e990020c059c770f85e3be729ad324872e429"
65
+ },
66
+ {
67
+ "device_id": "DEV0017",
68
+ "fingerprint_hash": "90348642cc0b1e13e5b07e17d185255df894eb65ab2dc3d455bc85ec5a1fb1b0"
69
+ },
70
+ {
71
+ "device_id": "DEV0018",
72
+ "fingerprint_hash": "b0cea960e30dabadbcce56690cc31a9b014e69495d6ed0a3bcc1a8dba1f560c5"
73
+ },
74
+ {
75
+ "device_id": "DEV0019",
76
+ "fingerprint_hash": "95ac359e7f63862c3b000ff6d7823988535a6d46d63691be7671be8ae5df10a9"
77
+ },
78
+ {
79
+ "device_id": "DEV0020",
80
+ "fingerprint_hash": "09bd5e6e6fbd47340b2e522f08673748ad4b221bb13acde2f3c852129d58feaa"
81
+ },
82
+ {
83
+ "device_id": "DEV0021",
84
+ "fingerprint_hash": "ada149be46843f511551113fd3be1c4bcac48adc698a6742dff5642441bae07f"
85
+ },
86
+ {
87
+ "device_id": "DEV0022",
88
+ "fingerprint_hash": "28ae808824e145ae8d20deebf8379231942fa3a936dbee29a231f483471535c5"
89
+ },
90
+ {
91
+ "device_id": "DEV0023",
92
+ "fingerprint_hash": "dd93d76752cf7a23c9ffeae68b31852f920a2120867e579a9ec6b8751b13b19d"
93
+ },
94
+ {
95
+ "device_id": "DEV0024",
96
+ "fingerprint_hash": "66f59f5593724ee82d76af4b65b29bc37b6a5e929acd8c9c4edc7bc1faf0754c"
97
+ },
98
+ {
99
+ "device_id": "DEV0025",
100
+ "fingerprint_hash": "e247d3458befeaa26ba592d5efb3c6a9d7e931266153015768bd7e43a6dc4f18"
101
+ },
102
+ {
103
+ "device_id": "DEV0026",
104
+ "fingerprint_hash": "15c786452a55638b8e1ef4741f1919d391e4206766d8db03ecfab29abc08ce9c"
105
+ },
106
+ {
107
+ "device_id": "DEV0027",
108
+ "fingerprint_hash": "11987d41b7ebe2e0e08d64441810c22dd5406d981e686b9122c6fc75cda8ea51"
109
+ },
110
+ {
111
+ "device_id": "DEV0028",
112
+ "fingerprint_hash": "ee9b014777a4a7f42658efbaaaeecdb56ae54417649ccba9e315c49f4d029f55"
113
+ },
114
+ {
115
+ "device_id": "DEV0029",
116
+ "fingerprint_hash": "82c4fcc7343a13da1d8ca1995630bf94e4c48dc82aedaa0d6a940c9b877fe828"
117
+ },
118
+ {
119
+ "device_id": "DEV0030",
120
+ "fingerprint_hash": "3949979a4ed3302445afa9090c2400e68ce3cf1626f5bd5c56cf8e9beb0c324f"
121
+ },
122
+ {
123
+ "device_id": "DEV0031",
124
+ "fingerprint_hash": "bf180a97506eddf73f797624d1ca2747fe072a680d1f8feb87522e3a2863ab0d"
125
+ },
126
+ {
127
+ "device_id": "DEV0032",
128
+ "fingerprint_hash": "35e77b95db18460dcee46976eefdcb656b2538e568caa5c6464107f5b184b43f"
129
+ },
130
+ {
131
+ "device_id": "DEV0033",
132
+ "fingerprint_hash": "385d83f1e711a6c59b3dae4d4370becdd450d406f5484bdd1d2de4be6ee162dd"
133
+ },
134
+ {
135
+ "device_id": "DEV0034",
136
+ "fingerprint_hash": "02452a07c7d59ae73938060771f3a3c9e63817115d6c38d1509864019629f455"
137
+ },
138
+ {
139
+ "device_id": "DEV0035",
140
+ "fingerprint_hash": "070b608bd70d53b152dca52ca12317b6765cd688be2a88d5de4756b956e53231"
141
+ },
142
+ {
143
+ "device_id": "DEV0036",
144
+ "fingerprint_hash": "1aeae6eda5fb48cb52f612f619fc19dead171885df81e0a24ef8422f4e5a2b3f"
145
+ },
146
+ {
147
+ "device_id": "DEV0037",
148
+ "fingerprint_hash": "04897f616fa40d3c6f5a183e62e1b4baf5b84a54067fbbc4030f715a5ce85f52"
149
+ },
150
+ {
151
+ "device_id": "DEV0038",
152
+ "fingerprint_hash": "36347a0ba4c5f8a2b59939b278decd023ae03afad64fd19952d010fdc03e2f93"
153
+ },
154
+ {
155
+ "device_id": "DEV0039",
156
+ "fingerprint_hash": "19aad1bf58c2ea6d35b23ba6f8c4e8f74098097013e178306f24dee7a5296b0c"
157
+ },
158
+ {
159
+ "device_id": "DEV0040",
160
+ "fingerprint_hash": "d67ebb698454aaa8428ddbaf5c7c4f0ca27da2a3e62da0ec863ef921ecbb7f34"
161
+ },
162
+ {
163
+ "device_id": "DEV0041",
164
+ "fingerprint_hash": "cfe8eccabfbe132b62a82adaf7c1c5c9b7566a14e86236361374a3f2429a94d8"
165
+ },
166
+ {
167
+ "device_id": "DEV0042",
168
+ "fingerprint_hash": "b935a681b5961d6e0fcfaffb5a444c09eebe778ad40335225882706f6ad50f21"
169
+ },
170
+ {
171
+ "device_id": "DEV0043",
172
+ "fingerprint_hash": "5f530db327760254140d9436404e37d60ca32d8e0158516621e0e447c2292b0e"
173
+ },
174
+ {
175
+ "device_id": "DEV0044",
176
+ "fingerprint_hash": "c0a6b17ecc53ae4b3ffb43c273f61ef0e32eb9c616a30ed16a213b23b63e322c"
177
+ },
178
+ {
179
+ "device_id": "DEV0045",
180
+ "fingerprint_hash": "08e48a8732db10b3fcf8c108070736fce2876c5ffbe0f8bade0483c686905aba"
181
+ },
182
+ {
183
+ "device_id": "DEV0046",
184
+ "fingerprint_hash": "cf2fde6fa6d2405f45eac3d626ee25b406d8a060ea9158439ba188b33a4a3c0f"
185
+ },
186
+ {
187
+ "device_id": "DEV0047",
188
+ "fingerprint_hash": "c127cdbe3b45ed343cacdd309a58132392becf54e617b3b79aec8a191eaf015d"
189
+ },
190
+ {
191
+ "device_id": "DEV0048",
192
+ "fingerprint_hash": "61a2d4085c8248d06dd7c68c21e809a1a24bbcc6cc7f2ab2b2296dc98f5127b0"
193
+ },
194
+ {
195
+ "device_id": "DEV0049",
196
+ "fingerprint_hash": "88eaec76dac2f2438c7bebd81f9dbd0fe4538ecf14b3ff6fe4639f99f02c8a5f"
197
+ },
198
+ {
199
+ "device_id": "DEV0050",
200
+ "fingerprint_hash": "ca61a32fb5241c5b38f30ef63328eca2816cf15c8b35303c7ae9d7671a599bd8"
201
+ },
202
+ {
203
+ "device_id": "DEV0051",
204
+ "fingerprint_hash": "3b0151220be1f21df3293068efa7fe45d4076a9c401ed5e63aa73ab66d9fe696"
205
+ },
206
+ {
207
+ "device_id": "DEV0052",
208
+ "fingerprint_hash": "508c6ceaeb33916ee8b93c51f2b33309df303143351cd680317a0a221b794ab4"
209
+ },
210
+ {
211
+ "device_id": "DEV0053",
212
+ "fingerprint_hash": "6a5c6fc453aca12e208960d9d81629c889ade125b728ec1e7e099daca2f65163"
213
+ },
214
+ {
215
+ "device_id": "DEV0054",
216
+ "fingerprint_hash": "aed1fc09f50aabc03df59d27779c2dd5748acc98f1a1d91e22b1a9a5bade0f1b"
217
+ },
218
+ {
219
+ "device_id": "DEV0055",
220
+ "fingerprint_hash": "7e69d25fcb6a833c665622c788b2f8c4b80acc9f4c9dd9a105d56d415ac352df"
221
+ },
222
+ {
223
+ "device_id": "DEV0056",
224
+ "fingerprint_hash": "11eaac8509ad85a68ae64ce5c7024f833e44d6f1f7f71bf5a1b8a8f96c694e45"
225
+ },
226
+ {
227
+ "device_id": "DEV0057",
228
+ "fingerprint_hash": "d25e4e14d7c33840032cd72129ab233e57d854bda2bdb7ffab4cb596e29e6e34"
229
+ },
230
+ {
231
+ "device_id": "DEV0058",
232
+ "fingerprint_hash": "39f30301a776f5138f910aef0ade88f967829704f9d09ac71968c3a65b0fbcac"
233
+ },
234
+ {
235
+ "device_id": "DEV0059",
236
+ "fingerprint_hash": "52e183394b013d780ecf97c25a8d93794bbf40c5d589d2c7d32617153e62675f"
237
+ },
238
+ {
239
+ "device_id": "DEV0060",
240
+ "fingerprint_hash": "24fa85425cafa2a1797f09e9edd43862674ec0808202b573311cdce269abcdc5"
241
+ },
242
+ {
243
+ "device_id": "DEV0061",
244
+ "fingerprint_hash": "4b3ad38a628d961308e0ac9f295876f6ee9b1754fb95921a2d6eb42306923432"
245
+ },
246
+ {
247
+ "device_id": "DEV0062",
248
+ "fingerprint_hash": "f78512d228cbcd485d84a5797ad61ac3a48a8c3324ad2cb0bb82a5873dda2d43"
249
+ },
250
+ {
251
+ "device_id": "DEV0063",
252
+ "fingerprint_hash": "7d6b6550c63d369386428975597f30d0188246cc1b01d993e67853b9df636301"
253
+ },
254
+ {
255
+ "device_id": "DEV0064",
256
+ "fingerprint_hash": "1701d293a290a967c56fb940b5b1422b732bfd40cb6529afe3b8b8cd4758cffb"
257
+ },
258
+ {
259
+ "device_id": "DEV0065",
260
+ "fingerprint_hash": "593a3dbea28a2dcb92e04be796fa35ed20b2913dbb2e2af49b549f15d62b01b3"
261
+ },
262
+ {
263
+ "device_id": "DEV0066",
264
+ "fingerprint_hash": "c6e44d279a50e5762dd8952e0645a6819c7c1029e6bd2e80c6685545357d8b77"
265
+ },
266
+ {
267
+ "device_id": "DEV0067",
268
+ "fingerprint_hash": "6a0f79364d6c803e9e98ef1ab6d792bab13f500d5ddb2f2fe82e913b306f1b23"
269
+ },
270
+ {
271
+ "device_id": "DEV0068",
272
+ "fingerprint_hash": "833d50d4829bc0423421094c01bd1db405470b6b01faca640a4ba3e379e43112"
273
+ },
274
+ {
275
+ "device_id": "DEV0069",
276
+ "fingerprint_hash": "8cb6607072ce80bf74deaf9ebdca568d2c2e103fe549948db981cda1460bd325"
277
+ },
278
+ {
279
+ "device_id": "DEV0070",
280
+ "fingerprint_hash": "e47b3123946724dda2cab2a22db7da4bca1a1fddd5027c3d295600898f017578"
281
+ },
282
+ {
283
+ "device_id": "DEV0071",
284
+ "fingerprint_hash": "1fc951170dda341c54c80edb97e05842375a0a3e23568705e85bb9f7dbf81658"
285
+ },
286
+ {
287
+ "device_id": "DEV0072",
288
+ "fingerprint_hash": "eda05f844426790623c45c1a02431dcdbfbbde54e8c973759529b198760edf5d"
289
+ },
290
+ {
291
+ "device_id": "DEV0073",
292
+ "fingerprint_hash": "435a3b6bcf4051c62669d3542f57028d4d1ba0137a36f9f38a633332a5303649"
293
+ },
294
+ {
295
+ "device_id": "DEV0074",
296
+ "fingerprint_hash": "9bee8c9bfb9e65c9c300698d6e4e64e2aa99e5f5c36e7a0ab436d01b2c723d9c"
297
+ },
298
+ {
299
+ "device_id": "DEV0075",
300
+ "fingerprint_hash": "b617d2dde84b3bf25343094d5809c3a25fb56cd53ab79026249c5ada18b785c4"
301
+ },
302
+ {
303
+ "device_id": "DEV0076",
304
+ "fingerprint_hash": "8cfc0db2290e2f2bc460b4ec922d4a7276628e252215255b0f53ef1d5710c258"
305
+ },
306
+ {
307
+ "device_id": "DEV0077",
308
+ "fingerprint_hash": "88e8b456d78265c3342f618b3423eb8abbbccd8eed6937592c78723eccf7411a"
309
+ },
310
+ {
311
+ "device_id": "DEV0078",
312
+ "fingerprint_hash": "a8b66885ca54d4c85c107e6c27b948c7086bb67244470950347b7c7038470472"
313
+ },
314
+ {
315
+ "device_id": "DEV0079",
316
+ "fingerprint_hash": "ba15256e56e10ac5743d671438b3e11dc8e4b572a90f49d166f289bcfc47e7ec"
317
+ },
318
+ {
319
+ "device_id": "DEV0080",
320
+ "fingerprint_hash": "0b9a7c5dd71c9fae593b631c74f562858113bef1919f876b7bfb1995e84bf18a"
321
+ },
322
+ {
323
+ "device_id": "DEV0081",
324
+ "fingerprint_hash": "b60dce9f2842489e093d7bc9cffec7d253dfe515c2ad5ceeeee55b459daa2cb7"
325
+ },
326
+ {
327
+ "device_id": "DEV0082",
328
+ "fingerprint_hash": "cc9c8955681c577a3972d4a1004c6c2d2da09e4307958efdb91de32387c4957c"
329
+ },
330
+ {
331
+ "device_id": "DEV0083",
332
+ "fingerprint_hash": "8741acbe2d0c6b84021c5f1a68d55344a1998f658d41824cbe6686447d61c499"
333
+ },
334
+ {
335
+ "device_id": "DEV0084",
336
+ "fingerprint_hash": "3a67180bd370329e3d135c153cc84db335eaf25bc782dfe6c8933b74983aa201"
337
+ },
338
+ {
339
+ "device_id": "DEV0085",
340
+ "fingerprint_hash": "d427efd1ca295210073ae45fbcdabdc077344c7debb3c8efd22fae5697b82322"
341
+ },
342
+ {
343
+ "device_id": "DEV0086",
344
+ "fingerprint_hash": "bafc63bb7efdabdeecb6a7ec9d74e4fa9c884bc54972f4c1f2f72a260cc48c96"
345
+ },
346
+ {
347
+ "device_id": "DEV0087",
348
+ "fingerprint_hash": "39ac507dc3ef4d086fa60d8dc9a5be789e5a8bbd28af301d6e5d6265e95d7d6e"
349
+ },
350
+ {
351
+ "device_id": "DEV0088",
352
+ "fingerprint_hash": "817aa050481c4747e0505cbee4ea854e38ec50289cdfd24058e37959eab5509d"
353
+ },
354
+ {
355
+ "device_id": "DEV0089",
356
+ "fingerprint_hash": "29327096afa18aae754feccd40ea64aec5abcef342199322e718945f2439e911"
357
+ },
358
+ {
359
+ "device_id": "DEV0090",
360
+ "fingerprint_hash": "853681c1b26e7425d9b5147565e1ff775de1bfbc1be06161a5a1cd2cb9d113d4"
361
+ },
362
+ {
363
+ "device_id": "DEV0091",
364
+ "fingerprint_hash": "73e99ec7f1044cbd2318bd1c7ce3648a9d6a83797c2f69aacc7f028fe9942ff8"
365
+ },
366
+ {
367
+ "device_id": "DEV0092",
368
+ "fingerprint_hash": "41bc4b2975b1a1f96b919175e93bafe6750f3d2075a308ddf5b1754c17308d91"
369
+ },
370
+ {
371
+ "device_id": "DEV0093",
372
+ "fingerprint_hash": "b73548c18699ce47a63cba0d6632c1b704bf1a77ec74ddafbe0b7247c010b72b"
373
+ },
374
+ {
375
+ "device_id": "DEV0094",
376
+ "fingerprint_hash": "42213b8e1e6ce68c23aea644889bd79c2c1c116253f98dbea48ace33928c9093"
377
+ },
378
+ {
379
+ "device_id": "DEV0095",
380
+ "fingerprint_hash": "7a8c66680e4644618e21c8af793aad5fa54c5ecb67b3c0209ce0352982340be5"
381
+ },
382
+ {
383
+ "device_id": "DEV0096",
384
+ "fingerprint_hash": "f696db148acfdf2419d3f9ab885f62373d7b081e846e452045140839e320f7ae"
385
+ },
386
+ {
387
+ "device_id": "DEV0097",
388
+ "fingerprint_hash": "54e99a8b6818a9cb2c563c607fbb8c7d57721140b672eca70ddfe50f742cd9bb"
389
+ },
390
+ {
391
+ "device_id": "DEV0098",
392
+ "fingerprint_hash": "3975220db138f0e5ae60abb0ee590a18c9d5aeaf704dfd3ce8886b3d2dd4a165"
393
+ },
394
+ {
395
+ "device_id": "DEV0099",
396
+ "fingerprint_hash": "c71abbab99a200f2b44e74fa2db31eb6d7cd36251c6764aaa82fa6153047957c"
397
+ },
398
+ {
399
+ "device_id": "DEV0100",
400
+ "fingerprint_hash": "91fdbcb265b4e0f06b0dd9ea04f1de208497dd1626e8784057a6b4c5cfaf39e3"
401
+ },
402
+ {
403
+ "device_id": "DEV0101",
404
+ "fingerprint_hash": "a1947716850a2b2a0ff985aed22b25b3668b2b3cca1270adace18f703f6d25f2"
405
+ },
406
+ {
407
+ "device_id": "DEV0102",
408
+ "fingerprint_hash": "11df1f2a615dd7ce447ebbec5d6a89f328ed46685f1fd50be02d4f5749c601f3"
409
+ },
410
+ {
411
+ "device_id": "DEV0103",
412
+ "fingerprint_hash": "3d85970bad02acfdc6e9a6dfab738d26a4102b5b9bc2484008920982303c8d54"
413
+ },
414
+ {
415
+ "device_id": "DEV0104",
416
+ "fingerprint_hash": "0e3e3040d29b0b93da8cce005cf96febf46e6a7c01c6b4ad56f59d9cd67ee664"
417
+ },
418
+ {
419
+ "device_id": "DEV0105",
420
+ "fingerprint_hash": "24078f2a3924aae7ae12b4cb88ca8e7631b887d3fb93d3cd9615f18326864dfd"
421
+ },
422
+ {
423
+ "device_id": "DEV0106",
424
+ "fingerprint_hash": "4d3cf93f98b9ce67bf7753c2c65874b855f80e52cd6340dcebf00ad90d164f01"
425
+ },
426
+ {
427
+ "device_id": "DEV0107",
428
+ "fingerprint_hash": "b4b5f295d77209a1bc23a06d15fc5dc9d31804844ed47995ea4e1e9cfd148cf8"
429
+ },
430
+ {
431
+ "device_id": "DEV0108",
432
+ "fingerprint_hash": "83f6ed958b524931453f05f6ac6a8fd585dcf1dbf45ce76be59bc0462318193e"
433
+ },
434
+ {
435
+ "device_id": "DEV0109",
436
+ "fingerprint_hash": "3f51ed22788be7ce2b8d4cb5a435766cf2725b37061f6fcd27aaec20dbef746e"
437
+ },
438
+ {
439
+ "device_id": "DEV0110",
440
+ "fingerprint_hash": "0228e1ecc4f4ec1389c84f5e18c3872a3adc1615e2ce472c2c29ac050dd42a29"
441
+ },
442
+ {
443
+ "device_id": "DEV0111",
444
+ "fingerprint_hash": "e9d645dd83efe4cd562b73710254625d0d27ba8d6f2b896922868146011bf880"
445
+ },
446
+ {
447
+ "device_id": "DEV0112",
448
+ "fingerprint_hash": "f1b79819a725cf8540e185f7d65c030ae0bc2a889028b2925b138e98db7720f2"
449
+ },
450
+ {
451
+ "device_id": "DEV0113",
452
+ "fingerprint_hash": "b4d74a90f99ca43f8245cb0e4448785e51b9fd38a1df23c95332a6a3becf3a2a"
453
+ },
454
+ {
455
+ "device_id": "DEV0114",
456
+ "fingerprint_hash": "8cc68ca33e2d1a114d58215f396ef7f64ad017093e85885ca120ed9b5abe7a85"
457
+ },
458
+ {
459
+ "device_id": "DEV0115",
460
+ "fingerprint_hash": "f14ef2e9ac6efad90d2146d12f9e938cccd9b99498426caae15595ad71548e11"
461
+ },
462
+ {
463
+ "device_id": "DEV0116",
464
+ "fingerprint_hash": "0f8e632cf92aee8766989ed8d5673bb1d6677ca631c5adbdb1c06dc319c8140d"
465
+ },
466
+ {
467
+ "device_id": "DEV0117",
468
+ "fingerprint_hash": "31a317db95f8fa02ba39ec7eda32642c28946ff34d4cfcbd7d8c50846fb27990"
469
+ },
470
+ {
471
+ "device_id": "DEV0118",
472
+ "fingerprint_hash": "2ea8c20e389601eb119dd62202a3d18a1880ea6aa867da53d309c6fd37f5f61b"
473
+ },
474
+ {
475
+ "device_id": "DEV0119",
476
+ "fingerprint_hash": "adaf093609582ed99409272e44d76ca6d5ac9c8d076e955b80465197a1bc9f19"
477
+ },
478
+ {
479
+ "device_id": "DEV0120",
480
+ "fingerprint_hash": "5121f815bfb1639b79245f4b72b3b1f58fb2c91259d514ff713ba3615ac1f52e"
481
+ },
482
+ {
483
+ "device_id": "DEV0121",
484
+ "fingerprint_hash": "ea247c4bb3cba6cd75617c031f54da431304686119a9f3d7be23639f40ffc099"
485
+ },
486
+ {
487
+ "device_id": "DEV0122",
488
+ "fingerprint_hash": "26302b55508bdae671399da3870965494a485ba0701d8c01b1f0a9fa579810ab"
489
+ },
490
+ {
491
+ "device_id": "DEV0123",
492
+ "fingerprint_hash": "3ce8fd49960af87d309113e5b73a42d5e6db899b3689a826aac69b740060c6cc"
493
+ },
494
+ {
495
+ "device_id": "DEV0124",
496
+ "fingerprint_hash": "5252de879a6ce1b1c25270a458f2dc5473955462ab6a3c2c6ac8d37cac8b3f2f"
497
+ },
498
+ {
499
+ "device_id": "DEV0125",
500
+ "fingerprint_hash": "dbf386ebfc1134427e0710bcd250e7937cdcd77d06449a727f866d31f865001b"
501
+ },
502
+ {
503
+ "device_id": "DEV0126",
504
+ "fingerprint_hash": "ffdc1a0ee44a7d2a6066f826c860084d7a39198de7a288042ca249fbca9e6178"
505
+ },
506
+ {
507
+ "device_id": "DEV0127",
508
+ "fingerprint_hash": "b93c043125e7ac79650e85613a4b21b4e6edcdad28f3300da997cd2792518833"
509
+ },
510
+ {
511
+ "device_id": "DEV0128",
512
+ "fingerprint_hash": "dba1a97a422446923ddb8e9300700147a16c31479cb18ffdc4f103f022796462"
513
+ },
514
+ {
515
+ "device_id": "DEV0129",
516
+ "fingerprint_hash": "a52e480fa87f32ff9160a91b94d555dd1438765e36a158e92607cb3def940d03"
517
+ },
518
+ {
519
+ "device_id": "DEV0130",
520
+ "fingerprint_hash": "a139196ef3ce6f340448120c2e4a622092fc80d7b880227a656d1e957519ddb9"
521
+ },
522
+ {
523
+ "device_id": "DEV0131",
524
+ "fingerprint_hash": "1b82b311802452415be949f25d3475f4587bbf7ee01732dcce3ab8bf0552e05f"
525
+ },
526
+ {
527
+ "device_id": "DEV0132",
528
+ "fingerprint_hash": "dbbb1cb9ee3646719e6ae8170fb73ea5fd8406f403a6d32c31802f5033f8043f"
529
+ },
530
+ {
531
+ "device_id": "DEV0133",
532
+ "fingerprint_hash": "0140442ffe6f2815f974c243450ff57e38670d404bd7aa74072b4cda2b1b282c"
533
+ },
534
+ {
535
+ "device_id": "DEV0134",
536
+ "fingerprint_hash": "ac8feb34a99e6997a3d556ab2a0e833cc2ad09f336ebc3481787b4ff9448db67"
537
+ },
538
+ {
539
+ "device_id": "DEV0135",
540
+ "fingerprint_hash": "6138063d33ea88fc5d6f6b8c4249cb47934c8c272018fa2f127ac56003b531ad"
541
+ },
542
+ {
543
+ "device_id": "DEV0136",
544
+ "fingerprint_hash": "52597e82355f2a4a60f599d68f48416af919c67d60effd2beae178d61f664127"
545
+ },
546
+ {
547
+ "device_id": "DEV0137",
548
+ "fingerprint_hash": "650301a2c0289b93daf03894c43b20fca874dfa48eb9977e9b50d4497ce61bd1"
549
+ },
550
+ {
551
+ "device_id": "DEV0138",
552
+ "fingerprint_hash": "c107bbbadad87b75ffa29b367aa6d83b61ebf3c9b8c9e50477039870df596e8b"
553
+ },
554
+ {
555
+ "device_id": "DEV0139",
556
+ "fingerprint_hash": "e493efd4b5b56aa025cdc6a4db42b70a45d0faaa367792dd8c5788dd92ae2137"
557
+ },
558
+ {
559
+ "device_id": "DEV0140",
560
+ "fingerprint_hash": "f9e603caa31e2d663a62b9626ce0429c79a5e29e7e586c9703f8a998ffe77d7e"
561
+ },
562
+ {
563
+ "device_id": "DEV0141",
564
+ "fingerprint_hash": "c5ffe5ebca9062920bd1f5a3288b01216ff26ff7dfd16be8286cdbf16f5452a9"
565
+ },
566
+ {
567
+ "device_id": "DEV0142",
568
+ "fingerprint_hash": "0a67370ec6ff33ccd8df042e4e2d42ec9f6b73dd65a694eaa88c59627ccb7e55"
569
+ },
570
+ {
571
+ "device_id": "DEV0143",
572
+ "fingerprint_hash": "cd074ee306428c4c335ebc302c0f9f1c06a31a0c5088f2cb6a06308fb01d7f6e"
573
+ },
574
+ {
575
+ "device_id": "DEV0144",
576
+ "fingerprint_hash": "a30e6012968ce08aca308c81e0d5297ea387c0becbbdff83e69a518ba8f581c1"
577
+ },
578
+ {
579
+ "device_id": "DEV0145",
580
+ "fingerprint_hash": "721d0ff76c1bb31718fb7ad3e34c624d235f2a69a388ccac9a900efc1012b763"
581
+ },
582
+ {
583
+ "device_id": "DEV0146",
584
+ "fingerprint_hash": "735e4f332bcae2d65888e20f78ca33d2cb5a61a6af8b9d639691ba7fa912d30d"
585
+ },
586
+ {
587
+ "device_id": "DEV0147",
588
+ "fingerprint_hash": "2551ffb8d9624a19de5b38c18e96ee22a3ca589e6b2e81c92b6810b5904de2e6"
589
+ },
590
+ {
591
+ "device_id": "DEV0148",
592
+ "fingerprint_hash": "28b3d8e8661c5fe68c3adb1d8b39c3ad3cad5583d8d5ee436a5992a97c78e3f1"
593
+ },
594
+ {
595
+ "device_id": "DEV0149",
596
+ "fingerprint_hash": "c5821128931ca2fe459884b61732177af5e72dd4bfdad39f90c5853570f60084"
597
+ },
598
+ {
599
+ "device_id": "DEV0150",
600
+ "fingerprint_hash": "f3ac4b7a6b2cbc0ee82f77863ced05418effa7245f7af41a5e9dd4bbf7e5ee2d"
601
+ },
602
+ {
603
+ "device_id": "DEV0151",
604
+ "fingerprint_hash": "18256de44a5ff0d275facfdf91076d360b28618e09e30789dc61e140f4175b84"
605
+ },
606
+ {
607
+ "device_id": "DEV0152",
608
+ "fingerprint_hash": "d3a693d2859e142bd62896745814ba2d480488c85d8e19f2ca1f5a5d1193cf9c"
609
+ },
610
+ {
611
+ "device_id": "DEV0153",
612
+ "fingerprint_hash": "cc68965e9c4b321388dafd285335e1ab7022f4730c32297c825e9ffd406b5f05"
613
+ },
614
+ {
615
+ "device_id": "DEV0154",
616
+ "fingerprint_hash": "30cb9f08032174e5c6dd523f72e258d6884fcdb73957b1569586388c7ab00180"
617
+ },
618
+ {
619
+ "device_id": "DEV0155",
620
+ "fingerprint_hash": "b13a743e1569533dd0a0bbe43a26505df8c774167a25bc236794c622196885c7"
621
+ },
622
+ {
623
+ "device_id": "DEV0156",
624
+ "fingerprint_hash": "ec955baadd25ee1acb240cc18501f5f9b95c922848205dab7e9c52872759dcdc"
625
+ },
626
+ {
627
+ "device_id": "DEV0157",
628
+ "fingerprint_hash": "c970edd75ce821717e1611fbe03b372b6a38826841a24b5c6984cd86013d3b3b"
629
+ },
630
+ {
631
+ "device_id": "DEV0158",
632
+ "fingerprint_hash": "73ffa03d52bc3863276f3e295126e6cce5ee0ab35c13cb0406dac8e3226b4fb8"
633
+ },
634
+ {
635
+ "device_id": "DEV0159",
636
+ "fingerprint_hash": "262d1db466cb3cf4793433f66e35d494bddf2fc59c5b19138a4d21c398a5ff64"
637
+ },
638
+ {
639
+ "device_id": "DEV0160",
640
+ "fingerprint_hash": "03a317cb2a5f02a977952ba1247f9b038efb3dbd3efe86c6f9c48c7dc1cfc9df"
641
+ },
642
+ {
643
+ "device_id": "DEV0161",
644
+ "fingerprint_hash": "e62e5ea09c36d9041b6deee7c60dea3b7d05bd0f4e952244fcc2015da6f61859"
645
+ },
646
+ {
647
+ "device_id": "DEV0162",
648
+ "fingerprint_hash": "63dac30d619826d0b5599ab0d1d9073e0e15181ca13abbc3c31ff1977befb922"
649
+ },
650
+ {
651
+ "device_id": "DEV0163",
652
+ "fingerprint_hash": "edfc9813f8bee36ca7216bb8981bdbb3a374406c2ab58f5351a062dd415016f8"
653
+ },
654
+ {
655
+ "device_id": "DEV0164",
656
+ "fingerprint_hash": "19c390de45b31cbb776a6998c0f7fbd79c5e872c79a957aaa869bc93876f4b18"
657
+ },
658
+ {
659
+ "device_id": "DEV0165",
660
+ "fingerprint_hash": "ba91e9204373aee6955dc387ed84ecc395baf199f0d1ef2ef79ae26c37027e13"
661
+ },
662
+ {
663
+ "device_id": "DEV0166",
664
+ "fingerprint_hash": "7de60c23c00257130dbc2a5e08f298a247136149f0677d16e2961620806c9d37"
665
+ },
666
+ {
667
+ "device_id": "DEV0167",
668
+ "fingerprint_hash": "3d1c715cae8a61bf5d23450f090fbec7f78dfde039932cd367bcb5a77aaf5ca5"
669
+ },
670
+ {
671
+ "device_id": "DEV0168",
672
+ "fingerprint_hash": "fe7e8a3c0f0361b524fb7ca709e30190f80673ccd1062e95e8d36d4c81674430"
673
+ },
674
+ {
675
+ "device_id": "DEV0169",
676
+ "fingerprint_hash": "10ab263deda4fd612565c0afbe90ac77480cae22c3c294d9831f6744903d954f"
677
+ },
678
+ {
679
+ "device_id": "DEV0170",
680
+ "fingerprint_hash": "35be714a8ab083931ed33967d7f5a7c9f5d4d7623ac3c5f467d04344156482c8"
681
+ },
682
+ {
683
+ "device_id": "DEV0171",
684
+ "fingerprint_hash": "5f712ce44385b9425a21f5515cc9b1f2dae60f4966edeaeca67051a615435f35"
685
+ },
686
+ {
687
+ "device_id": "DEV0172",
688
+ "fingerprint_hash": "6e12751bebcde9f031d952f8ba517b33ecbfff0b35d57c268d6c31f91aae0f47"
689
+ },
690
+ {
691
+ "device_id": "DEV0173",
692
+ "fingerprint_hash": "eaeae7b62113013a4caad474c4bdb21625805de361025e403186ead5aa8b73c9"
693
+ },
694
+ {
695
+ "device_id": "DEV0174",
696
+ "fingerprint_hash": "edcbdab7ffa6a2ef975273b8697b64f6a31d1cd3be2403dc711dc047038a3f2f"
697
+ },
698
+ {
699
+ "device_id": "DEV0175",
700
+ "fingerprint_hash": "08242d9884fa31e09c1674ac77943077b3de52403cb7ae7d81f54294a02209b4"
701
+ },
702
+ {
703
+ "device_id": "DEV0176",
704
+ "fingerprint_hash": "7427e46e031bd27522540429f46d378b0017ca3a9db0816d4d4fe2a67f1786b2"
705
+ },
706
+ {
707
+ "device_id": "DEV0177",
708
+ "fingerprint_hash": "dfdef0d703d63aa90cb8f0ee363c42f5fc5116dc0f76937b24e6970b1f11a73a"
709
+ },
710
+ {
711
+ "device_id": "DEV0178",
712
+ "fingerprint_hash": "80b43b595727d6537c8dddbff9ef04e7d00314376a03d96b0066c01ccf18460e"
713
+ },
714
+ {
715
+ "device_id": "DEV0179",
716
+ "fingerprint_hash": "d311d35fd114608dd7e6bf5fc398f17451dc46a428cb2c3a8701ceba95e69156"
717
+ },
718
+ {
719
+ "device_id": "DEV0180",
720
+ "fingerprint_hash": "399e8635b6f3069a22005dff8ed4e5f0d80b5814915cd9c1a5749e7014c7029b"
721
+ },
722
+ {
723
+ "device_id": "DEV0181",
724
+ "fingerprint_hash": "fb5f2cf6d6f5746debc8e9c02b6ca4aeb94ae9b649440c27b3ba4e1897d1f49c"
725
+ },
726
+ {
727
+ "device_id": "DEV0182",
728
+ "fingerprint_hash": "acc4bebbdcb38a35a44c119a2fed8169e4b6394c585f28cc3229f28b4966690f"
729
+ },
730
+ {
731
+ "device_id": "DEV0183",
732
+ "fingerprint_hash": "b5136197ca98f8887336e5be7c4d6eed64fb251f1410ce5315cb9b072c4d2aa5"
733
+ },
734
+ {
735
+ "device_id": "DEV0184",
736
+ "fingerprint_hash": "c42374f6dca661d5bcaa89688e58d953f128b70efae7b10b09cc770508d46de5"
737
+ },
738
+ {
739
+ "device_id": "DEV0185",
740
+ "fingerprint_hash": "9e16aef0ecccddbc66e103dff3c2e8b667b26847e0726211e052e02c98e5c9fa"
741
+ },
742
+ {
743
+ "device_id": "DEV0186",
744
+ "fingerprint_hash": "18048cab487bb65348cd3a6efd795d5e063663bd4112cf6922e480a292ed885e"
745
+ },
746
+ {
747
+ "device_id": "DEV0187",
748
+ "fingerprint_hash": "3fb4eaca83182b5a815767762aed1f40aea0ba50602a2155a589dc377910ce0f"
749
+ },
750
+ {
751
+ "device_id": "DEV0188",
752
+ "fingerprint_hash": "55302dccf712906bc73745b0d517f5d77c06710f955b507751e4474278f1d76c"
753
+ },
754
+ {
755
+ "device_id": "DEV0189",
756
+ "fingerprint_hash": "fbc3969a93fa1c94ad256775bc8435fd679ba369e90950566e918be09c3cc9fa"
757
+ },
758
+ {
759
+ "device_id": "DEV0190",
760
+ "fingerprint_hash": "f60d71779db65024275c182d8b022869a8548727647525c56e6fe964c59d6e56"
761
+ },
762
+ {
763
+ "device_id": "DEV0191",
764
+ "fingerprint_hash": "68df74fc2576a202deba87e7cd152ee7dabc1324e2927a8c297faab3d668a1d5"
765
+ },
766
+ {
767
+ "device_id": "DEV0192",
768
+ "fingerprint_hash": "30d2ea953e10c9903ccf9ec5c90fa8177e2637a5c12b9299a00fd35f57c52f82"
769
+ },
770
+ {
771
+ "device_id": "DEV0193",
772
+ "fingerprint_hash": "a68aef0c676939714c2435d3d875614469ed7b4d04c4e090ddffdf377b514581"
773
+ },
774
+ {
775
+ "device_id": "DEV0194",
776
+ "fingerprint_hash": "155549f7554f1c87cb90ba6521ff6469704d5abd37264cbc7f45910bd19aea4c"
777
+ },
778
+ {
779
+ "device_id": "DEV0195",
780
+ "fingerprint_hash": "8a465d34184ce268b7944c823086408a3b90d264e74fbc1aa81b2c09a3bea06a"
781
+ },
782
+ {
783
+ "device_id": "DEV0196",
784
+ "fingerprint_hash": "fde7ddc0dc3e9846613438b51af0a9969d34fe11be1ef3b4f83004ca30bcf570"
785
+ },
786
+ {
787
+ "device_id": "DEV0197",
788
+ "fingerprint_hash": "3bf43de90d07f3b736ed8f19ea917cc07d553a4e03cdbcc213ea50e00d40c5ce"
789
+ },
790
+ {
791
+ "device_id": "DEV0198",
792
+ "fingerprint_hash": "196717070ceec0f3fb164befeefb465237be2621be068096a1765b6abdb32794"
793
+ },
794
+ {
795
+ "device_id": "DEV0199",
796
+ "fingerprint_hash": "470b7e115da1bc634a8619f86a6fc3c048462f1017060a8a2c6c2e53958b3bb3"
797
+ },
798
+ {
799
+ "device_id": "DEV0200",
800
+ "fingerprint_hash": "1aed95af8648d31779ae39ac199fdd1dd8c4165402a688106e4f3d488d10371b"
801
+ },
802
+ {
803
+ "device_id": "DEV0201",
804
+ "fingerprint_hash": "3fd06f0484a53da6310369cf67e632163c7774a7ea96805b1a81978a98ba8add"
805
+ },
806
+ {
807
+ "device_id": "DEV0202",
808
+ "fingerprint_hash": "9916255472143fa1306823d2297bfa345acf80f2a8a3f50ca84428549292ddfb"
809
+ },
810
+ {
811
+ "device_id": "DEV0203",
812
+ "fingerprint_hash": "3bb8027a989fd42a5f2bd3d970e6a99eb37c3783c7559491110a06f303ec184a"
813
+ },
814
+ {
815
+ "device_id": "DEV0204",
816
+ "fingerprint_hash": "0166621dddb3d2de86c769bafa3819c450986a7e0f53d1f2f1fc4a07186a0b9e"
817
+ },
818
+ {
819
+ "device_id": "DEV0205",
820
+ "fingerprint_hash": "812934ee92c13fc065e6b96fd019160a13b544c51479e1aa0a16e24be4c67e46"
821
+ },
822
+ {
823
+ "device_id": "DEV0206",
824
+ "fingerprint_hash": "63f4690e1222a02e0c9ffdad7c6f9dc0573d52728e4af47e05a733718c497da0"
825
+ },
826
+ {
827
+ "device_id": "DEV0207",
828
+ "fingerprint_hash": "bf4d8a98eb635042702a7b116eb4d54eef1fc80eaffbbb0f7629ffb5d4321c7b"
829
+ },
830
+ {
831
+ "device_id": "DEV0208",
832
+ "fingerprint_hash": "4b3f01fc311c8f07e6b4fb2c872f2caf3b6062ef177c1ab8b53a23965af2ee0f"
833
+ },
834
+ {
835
+ "device_id": "DEV0209",
836
+ "fingerprint_hash": "bbcb3a3f4a3af5c569397bbad60189e5999f7b0caefaa342342238d625efccaa"
837
+ },
838
+ {
839
+ "device_id": "DEV0210",
840
+ "fingerprint_hash": "af497f21beec53d976f1e3ad30874512ad7f7471e4325a6e592eaa78af64ce30"
841
+ },
842
+ {
843
+ "device_id": "DEV0211",
844
+ "fingerprint_hash": "2243cee127dff765b3e230b7c29e7826d4661ac30e348996371e5d51acf4b3c3"
845
+ },
846
+ {
847
+ "device_id": "DEV0212",
848
+ "fingerprint_hash": "79cbd127722cf00c7c76832eb89146f2551ff8dab46a82306bed96d62e4b9eb4"
849
+ },
850
+ {
851
+ "device_id": "DEV0213",
852
+ "fingerprint_hash": "b430c419103350580942a33ab997fa8841de9a072f43a5b61db95bbe79f225ce"
853
+ },
854
+ {
855
+ "device_id": "DEV0214",
856
+ "fingerprint_hash": "a0dfeda412b8f551552fb8869ebc7fa7b56771969aadb4bc2de7265c2a1dc2b6"
857
+ },
858
+ {
859
+ "device_id": "DEV0215",
860
+ "fingerprint_hash": "01e54077f641101fb0dc7ce0fad0674132a16b02f1f5f71ecbc8d54b5589f2c4"
861
+ },
862
+ {
863
+ "device_id": "DEV0216",
864
+ "fingerprint_hash": "3ef25793ccaf095048f58b1364f60a38c1716ebf5c25686d892d4c42b004849e"
865
+ },
866
+ {
867
+ "device_id": "DEV0217",
868
+ "fingerprint_hash": "72dea3fa1aa9f1986ec5bb8e8e595bad2844428f88c8d9ba0d4cad29ca827c21"
869
+ },
870
+ {
871
+ "device_id": "DEV0218",
872
+ "fingerprint_hash": "159e132d8a2c24f930254b0677edb3341d2ee86f167dc0ae06c4f22e815206a8"
873
+ },
874
+ {
875
+ "device_id": "DEV0219",
876
+ "fingerprint_hash": "9457c6d94fb8955bc2c97466f8edb6a7243713bcba553c22bbdb609a0fb89218"
877
+ },
878
+ {
879
+ "device_id": "DEV0220",
880
+ "fingerprint_hash": "434d62d11cdbc2344233496218fa868b7d7bf9a2bda9fbd47a75b669f6d52b5f"
881
+ },
882
+ {
883
+ "device_id": "DEV0221",
884
+ "fingerprint_hash": "689569376bbbb0cbcd2b05ff33dde57b7a4d195c1e36d5abd8723dc2044b723b"
885
+ },
886
+ {
887
+ "device_id": "DEV0222",
888
+ "fingerprint_hash": "de233ffba5a87bfe44c9e7073dfa47b99aaf5d13e478db692e4829d0e3218609"
889
+ },
890
+ {
891
+ "device_id": "DEV0223",
892
+ "fingerprint_hash": "e75e8776b891663a64004274cd0e4f8df70749289d68c1efb8ba08653c13ba49"
893
+ },
894
+ {
895
+ "device_id": "DEV0224",
896
+ "fingerprint_hash": "22c70e03cf0c912d7107d1b2a237d6fdee00d40d7c1855109bd5d046198c61f5"
897
+ },
898
+ {
899
+ "device_id": "DEV0225",
900
+ "fingerprint_hash": "88ad94ba3f7c793aa14b76d04eb7025448db4553394813f9831f167c8db3ba42"
901
+ },
902
+ {
903
+ "device_id": "DEV0226",
904
+ "fingerprint_hash": "5eda154a3084620278b83268e82b447069a7a71a31cbe9b27d1898a325bba394"
905
+ },
906
+ {
907
+ "device_id": "DEV0227",
908
+ "fingerprint_hash": "b2e3117cb302b9b9abc4659776045fe4bf0364f24af83ede1529de9a82c49f36"
909
+ },
910
+ {
911
+ "device_id": "DEV0228",
912
+ "fingerprint_hash": "1a3f2cbe674b27967acd7736658058420ec919821324517c9848e397d615705b"
913
+ },
914
+ {
915
+ "device_id": "DEV0229",
916
+ "fingerprint_hash": "4a9ee2642b4b61b64de554ac270e7e953ab4aff5cb476e07ef2623d0a9828bf9"
917
+ },
918
+ {
919
+ "device_id": "DEV0230",
920
+ "fingerprint_hash": "20edef9b43f6c907111bc698aa619f69318aa2e98adacb25ebeaa653b744dea2"
921
+ },
922
+ {
923
+ "device_id": "DEV0231",
924
+ "fingerprint_hash": "376df9db5dd92f6dbea17173268051ae42842ebca0e932cb67adfd12e6bed64a"
925
+ },
926
+ {
927
+ "device_id": "DEV0232",
928
+ "fingerprint_hash": "79f0c5218b27ca1db5e4476dd262a4cb4f85b208d500ef805d7edbd5820ff45d"
929
+ },
930
+ {
931
+ "device_id": "DEV0233",
932
+ "fingerprint_hash": "e09aa73f5c449d5d8575c72ae27a5e5f0dbafa799fa66fca87782d036a9497c1"
933
+ },
934
+ {
935
+ "device_id": "DEV0234",
936
+ "fingerprint_hash": "7eb3aa19d259892111f9e9b39ff4fd3f5d48e245ec41caf589944e252b63dffc"
937
+ },
938
+ {
939
+ "device_id": "DEV0235",
940
+ "fingerprint_hash": "7218b5c8dd5d350846f511e479d2e56fbcae4e1db85daa3999bf1c1234d205c8"
941
+ },
942
+ {
943
+ "device_id": "DEV0236",
944
+ "fingerprint_hash": "d699a6537aea630a34a686ea16b0d22091d625741115fb72411c5acf5d0ff7a7"
945
+ },
946
+ {
947
+ "device_id": "DEV0237",
948
+ "fingerprint_hash": "0a9852fa27a3bc4038cad803a71f4dbb138e2d24b22d11fbf0315b813b11f989"
949
+ },
950
+ {
951
+ "device_id": "DEV0238",
952
+ "fingerprint_hash": "cf1d3099b6d156a361bc5fee5468d22d32e65e3a73a35bfd1115ed5ff15f439a"
953
+ },
954
+ {
955
+ "device_id": "DEV0239",
956
+ "fingerprint_hash": "3fc2edda05b4805b46664b5c2c7f91646ff8241a3d147fd12f80479cb8e34a3a"
957
+ },
958
+ {
959
+ "device_id": "DEV0240",
960
+ "fingerprint_hash": "e5774e5ee75075147fcd1d974be5a2eb83b824084eac7d861c958558ee6f966d"
961
+ },
962
+ {
963
+ "device_id": "DEV0241",
964
+ "fingerprint_hash": "9803fba0a77ed4b9482e86af5326239b50d98babb7e54b8ab986b661b77af7b4"
965
+ },
966
+ {
967
+ "device_id": "DEV0242",
968
+ "fingerprint_hash": "77c5a3d62c14ce322a51bf61eefd89dd6746baa9c1f0fd8d7e44b2b63e2669aa"
969
+ },
970
+ {
971
+ "device_id": "DEV0243",
972
+ "fingerprint_hash": "f88fe6c35365ace1b3ce78060ad824cf9279f133a932b091bc8cedf61779f46d"
973
+ },
974
+ {
975
+ "device_id": "DEV0244",
976
+ "fingerprint_hash": "a072252dc49a4f06c01539e34951a966be452f1329d2178c82293a887b54f7d6"
977
+ },
978
+ {
979
+ "device_id": "DEV0245",
980
+ "fingerprint_hash": "9e8a0c8abce51ce29ebc5cfd6fc68fc6037746d687e82a4bab7c0bd9aaaca636"
981
+ },
982
+ {
983
+ "device_id": "DEV0246",
984
+ "fingerprint_hash": "eeaf23bc17309e86206849c70e2038796fb56433b97ae8ac139a30a4d062caea"
985
+ },
986
+ {
987
+ "device_id": "DEV0247",
988
+ "fingerprint_hash": "0130eb1f30cb0a6fa78294d19e7fc7cde8f6f735cf1a5c88a385058c5f9c5bc1"
989
+ },
990
+ {
991
+ "device_id": "DEV0248",
992
+ "fingerprint_hash": "e08ab35cb947b9eaecab0b3314084b554275838956e394d6b86d91a912767247"
993
+ },
994
+ {
995
+ "device_id": "DEV0249",
996
+ "fingerprint_hash": "361f3024cae380e9b8061edf1d5d678087f72d69e0fe4d92264a7da854679194"
997
+ },
998
+ {
999
+ "device_id": "DEV0250",
1000
+ "fingerprint_hash": "940e9aed9510c520450126fe9ffc9162fa0f90521d34ae6c82308aca5a3ca28c"
1001
+ },
1002
+ {
1003
+ "device_id": "DEV0251",
1004
+ "fingerprint_hash": "45e76743c14d60d6624a559b24050c2a0da57af5e0dcb778c0ac9ec937158fdc"
1005
+ },
1006
+ {
1007
+ "device_id": "DEV0252",
1008
+ "fingerprint_hash": "b7cb19f546039f6018c5ab57d24c9abc0f6b38beff1d7b8f8e5f7e2388fe4d51"
1009
+ },
1010
+ {
1011
+ "device_id": "DEV0253",
1012
+ "fingerprint_hash": "2011dc6f9723fe4d62a6a83ee2fbb66bff88d69eb50d78df00b5332f47a53b46"
1013
+ },
1014
+ {
1015
+ "device_id": "DEV0254",
1016
+ "fingerprint_hash": "2bc0e4b1573a39910b25c224adf78f5a208b2c9e9ee0c69581984fa979ed8983"
1017
+ },
1018
+ {
1019
+ "device_id": "DEV0255",
1020
+ "fingerprint_hash": "e6bcc7a1ff1504ea43bf0077c22a4bf298f885aac584b064bda4556c2c653db5"
1021
+ },
1022
+ {
1023
+ "device_id": "DEV0256",
1024
+ "fingerprint_hash": "35a434f8fb1e4eda0eba55b969fe9a8c21a62cab301bfaf4f22774812fdd66fc"
1025
+ },
1026
+ {
1027
+ "device_id": "DEV0257",
1028
+ "fingerprint_hash": "a35e497c810174287ddde3c1fa0395d10e4d8c07af3465900ddcec6a28ff4e7d"
1029
+ },
1030
+ {
1031
+ "device_id": "DEV0258",
1032
+ "fingerprint_hash": "e485c5c5989595c09cf28550e0025728090528e05daea7959853a9a58f8ecc99"
1033
+ },
1034
+ {
1035
+ "device_id": "DEV0259",
1036
+ "fingerprint_hash": "97508201bc0fdc707cf88932607f02a86f68af7cd6e379bfbacdc72741e6fb06"
1037
+ },
1038
+ {
1039
+ "device_id": "DEV0260",
1040
+ "fingerprint_hash": "fe727d3d58ebbd52fe18ce7d7a511cbab09394a418fd07ac5159f729934d603d"
1041
+ },
1042
+ {
1043
+ "device_id": "DEV0261",
1044
+ "fingerprint_hash": "aef218c61649a7254d669ca7252acc60859fca2c572b677f675751f55afca419"
1045
+ },
1046
+ {
1047
+ "device_id": "DEV0262",
1048
+ "fingerprint_hash": "40aec91990555870f21788152adf5f62df4862570806062b98472cf2dff565ee"
1049
+ },
1050
+ {
1051
+ "device_id": "DEV0263",
1052
+ "fingerprint_hash": "94596f74aa2c8f4fea21917a2a1602be7a40ad5d0a81157b907ee5243b84f89a"
1053
+ },
1054
+ {
1055
+ "device_id": "DEV0264",
1056
+ "fingerprint_hash": "dd6575da4c3c0d996b18d3a249e3ec4fb6c5ca74e86e7ac91a8c627e3ba36314"
1057
+ },
1058
+ {
1059
+ "device_id": "DEV0265",
1060
+ "fingerprint_hash": "b14c03095bc14d5047ee9288f355d423144797b60b9a5a25e49a545923daace2"
1061
+ },
1062
+ {
1063
+ "device_id": "DEV0266",
1064
+ "fingerprint_hash": "c345b8bc61b558e4d721951dda3ebe120ce794894f369d58a21d3aac8ee801de"
1065
+ },
1066
+ {
1067
+ "device_id": "DEV0267",
1068
+ "fingerprint_hash": "8f079c8bde59314a5abf4b4204f47763111a0201aa45b9844bdf934f1214f9a4"
1069
+ },
1070
+ {
1071
+ "device_id": "DEV0268",
1072
+ "fingerprint_hash": "818f7ed297af84e3fb21d82bf2258e62dcaf8cf4595b6878c9df8de6cf185cd6"
1073
+ },
1074
+ {
1075
+ "device_id": "DEV0269",
1076
+ "fingerprint_hash": "b212ef972f70d9a305f8db4f8194f1895cb1ae8f9757887341c1bf635e32c755"
1077
+ },
1078
+ {
1079
+ "device_id": "DEV0270",
1080
+ "fingerprint_hash": "4a1e9beedf0802026ca15acefdbffa48f6c1c1b2a1d4f5a661d31645f9b3170f"
1081
+ },
1082
+ {
1083
+ "device_id": "DEV0271",
1084
+ "fingerprint_hash": "fa04e38b61bde2015f45df85cab4ef524d95b97e249d9d1d71b33923b0511c2f"
1085
+ },
1086
+ {
1087
+ "device_id": "DEV0272",
1088
+ "fingerprint_hash": "308d0ad5a47f101fca6824f75d4290c9a0605f3433b597466f110607ef85c1c0"
1089
+ },
1090
+ {
1091
+ "device_id": "DEV0273",
1092
+ "fingerprint_hash": "eb9ad067c8fb3774058140f2cce762de45c2369742df9175e5f69cdfc34ad762"
1093
+ },
1094
+ {
1095
+ "device_id": "DEV0274",
1096
+ "fingerprint_hash": "0411946c28629b5c574897d0e741147beb79406a17799172fb8494d2deff0d08"
1097
+ },
1098
+ {
1099
+ "device_id": "DEV0275",
1100
+ "fingerprint_hash": "f1d60cccec882aa844bc4adf031e0128b831f6f3dda5274ffd46160497fe7869"
1101
+ },
1102
+ {
1103
+ "device_id": "DEV0276",
1104
+ "fingerprint_hash": "56b6e4dba423f7a5dea21a7de3369b1beda058f0ba41970989333cabc8c96447"
1105
+ },
1106
+ {
1107
+ "device_id": "DEV0277",
1108
+ "fingerprint_hash": "0ae3d6cdfe244951e0ef6a388bc9c0717f96575255bd85fa4e6a81e8dc08e339"
1109
+ },
1110
+ {
1111
+ "device_id": "DEV0278",
1112
+ "fingerprint_hash": "05f9ec5f1fc4f2627d06046e46e5e2f4339a2b736914cd1ea8bc6f7d9537b6cd"
1113
+ },
1114
+ {
1115
+ "device_id": "DEV0279",
1116
+ "fingerprint_hash": "a02c75ae0aa9bf25d1eff0f6b4a42d166fe0dd51dd85b9656d052717c128ee1e"
1117
+ },
1118
+ {
1119
+ "device_id": "DEV0280",
1120
+ "fingerprint_hash": "ccc25f2c144d3a87db9fb0b0f78beaf568235375b95d2fadd1ba1ce526cb10a1"
1121
+ },
1122
+ {
1123
+ "device_id": "DEV0281",
1124
+ "fingerprint_hash": "f1eae9eb9074a96fbc1d9a85efcd2a62d8c6b26edd81bdd2a047a63612debf56"
1125
+ },
1126
+ {
1127
+ "device_id": "DEV0282",
1128
+ "fingerprint_hash": "0dba34249bf3758faf43772e874e4f570ec74b94a9ce0e08a70afb0b4704ba19"
1129
+ },
1130
+ {
1131
+ "device_id": "DEV0283",
1132
+ "fingerprint_hash": "799b9c978d16417fa96ed40ed5672bcee1baf092decc7d64f7dde926ec6673cd"
1133
+ },
1134
+ {
1135
+ "device_id": "DEV0284",
1136
+ "fingerprint_hash": "4fa0f0e227bcef0b5a418b27dc03cea0fde91aeca44639879e57c849951d3a5c"
1137
+ },
1138
+ {
1139
+ "device_id": "DEV0285",
1140
+ "fingerprint_hash": "17a16524c4c8db6ddca0c95f8484ecd560b1fa917d45533775f0df9643bc41eb"
1141
+ },
1142
+ {
1143
+ "device_id": "DEV0286",
1144
+ "fingerprint_hash": "f255c655bc371ccc72c60855f7dd2eecb3b4d049dd0b7580a760aa1e6917ebca"
1145
+ },
1146
+ {
1147
+ "device_id": "DEV0287",
1148
+ "fingerprint_hash": "42cb6adcc33bd094f6fb7bc27215cfacb12e2da9772e28d778b6d3b213ea4c44"
1149
+ },
1150
+ {
1151
+ "device_id": "DEV0288",
1152
+ "fingerprint_hash": "9884c9f3d31c4520bf51b7b6a86f5295055c48fd7e9dff4c2de429ad19c0c0f3"
1153
+ },
1154
+ {
1155
+ "device_id": "DEV0289",
1156
+ "fingerprint_hash": "63e04749b3acdd7d8b8ee6719e0e55a1200415bdd73d9afbdb217ed12f2d2dfa"
1157
+ },
1158
+ {
1159
+ "device_id": "DEV0290",
1160
+ "fingerprint_hash": "a5cb7af29ebb327d63fc53c95947b68d3b2fde3f42c48efe30f915b2f06f7330"
1161
+ },
1162
+ {
1163
+ "device_id": "DEV0291",
1164
+ "fingerprint_hash": "53db0e6254bfc42930a79acb60f131c7c335fd3ba6410e9e65bfc0767c4e1bb3"
1165
+ },
1166
+ {
1167
+ "device_id": "DEV0292",
1168
+ "fingerprint_hash": "196ce9eff0771823b670fdffd87a86a824180b4c4689e34652647c89ad2b6cd7"
1169
+ },
1170
+ {
1171
+ "device_id": "DEV0293",
1172
+ "fingerprint_hash": "75b4965fa965e6fe282fa84ad83153dfa39c511a927a8c1e1245356644340738"
1173
+ },
1174
+ {
1175
+ "device_id": "DEV0294",
1176
+ "fingerprint_hash": "d7cfd5bcddcc70d778aaf1569f13de81b2fdf861aaf5c8131d2fdce849514c3a"
1177
+ },
1178
+ {
1179
+ "device_id": "DEV0295",
1180
+ "fingerprint_hash": "226a74a548e1b68e061040bd4a15bc18de7200143f53b8411b8a77c02ce2c3cf"
1181
+ },
1182
+ {
1183
+ "device_id": "DEV0296",
1184
+ "fingerprint_hash": "96a97763395eecbfed4d35259f23b8445b5d880bb05e1c52607809102a6a7e8d"
1185
+ },
1186
+ {
1187
+ "device_id": "DEV0297",
1188
+ "fingerprint_hash": "4f94fdc30fb473d365c4f5c22325eba3ef4a3f1124b250d7be5d424f058a9542"
1189
+ },
1190
+ {
1191
+ "device_id": "DEV0298",
1192
+ "fingerprint_hash": "e0ead05768534c144dcb02e403ac009221fa218d46e9879aee2eda58104e6c5a"
1193
+ },
1194
+ {
1195
+ "device_id": "DEV0299",
1196
+ "fingerprint_hash": "7afd340e37cdae754441b2957375828bf78f49e6e1da9386d2ccead93664aa60"
1197
+ },
1198
+ {
1199
+ "device_id": "DEV0300",
1200
+ "fingerprint_hash": "478fa1e0bd46c71f36a41ea86aa124df311596972c889c89f4d6a64361460184"
1201
+ },
1202
+ {
1203
+ "device_id": "DEV0301",
1204
+ "fingerprint_hash": "ae3b48a177e6ea5825410f53698a05f4f125766e2dc0e2b096c6be4581239757"
1205
+ },
1206
+ {
1207
+ "device_id": "DEV0302",
1208
+ "fingerprint_hash": "530353fc4ca2e864505c2d8b41be50f0178117462afc99680b02a4f042b0ceba"
1209
+ },
1210
+ {
1211
+ "device_id": "DEV0303",
1212
+ "fingerprint_hash": "b94dc53c5f996b73a06d1d03384e77401b244bb186b8f4e7290342bb12b1e1bd"
1213
+ },
1214
+ {
1215
+ "device_id": "DEV0304",
1216
+ "fingerprint_hash": "097cd26a06a24db9a55a478fa2af082a469dcf07783b5d57efea758630750678"
1217
+ },
1218
+ {
1219
+ "device_id": "DEV0305",
1220
+ "fingerprint_hash": "f89ed8cea01b6a710988c5c8edb535e3c28e8bd3b34882af0cc21517c7706826"
1221
+ },
1222
+ {
1223
+ "device_id": "DEV0306",
1224
+ "fingerprint_hash": "342d8ebb34825e643d0009e899d584cba16905c113c27c384fe1650320e18877"
1225
+ },
1226
+ {
1227
+ "device_id": "DEV0307",
1228
+ "fingerprint_hash": "14ce23b877155a18113d840441bbde334ff214a75da160d49b5aae672878197a"
1229
+ },
1230
+ {
1231
+ "device_id": "DEV0308",
1232
+ "fingerprint_hash": "ef852f564bd35196718604f93edce4dfcd5f10b1dd00c0f9bce53a1bc77a5d70"
1233
+ },
1234
+ {
1235
+ "device_id": "DEV0309",
1236
+ "fingerprint_hash": "75ef62bad0ba4e4a8596d71bc28cbe9c4cbfc2e81aec002c6fc6a00d83232535"
1237
+ },
1238
+ {
1239
+ "device_id": "DEV0310",
1240
+ "fingerprint_hash": "27d91cab279adf59df1ba2717fa38058ee7641e26ada3ec7bb1c1293b57f38c3"
1241
+ },
1242
+ {
1243
+ "device_id": "DEV0311",
1244
+ "fingerprint_hash": "64ee79d9ea63b60c6c137a1ef834c6e7c4081893be4673cddc3bf5744c150528"
1245
+ },
1246
+ {
1247
+ "device_id": "DEV0312",
1248
+ "fingerprint_hash": "b6dcdf54f6e7dae07baa4dcf061f0d143438a85821e6c1a7c762f5ddef76b443"
1249
+ },
1250
+ {
1251
+ "device_id": "DEV0313",
1252
+ "fingerprint_hash": "73c6a13bd97ad4ddb142e05b3684222d3aa3d3ea492ac98d7a43a3d8de8dec21"
1253
+ },
1254
+ {
1255
+ "device_id": "DEV0314",
1256
+ "fingerprint_hash": "5593c852f7a6a271ed567b69f7977d1242ef28e54c315e443aa5ff73d9488946"
1257
+ },
1258
+ {
1259
+ "device_id": "DEV0315",
1260
+ "fingerprint_hash": "5482861ca7d115e8f2d8d52c63bb5bf354aa99622397c3edcff47df6968a61a9"
1261
+ },
1262
+ {
1263
+ "device_id": "DEV0316",
1264
+ "fingerprint_hash": "44495d2b8c99968fca1d0b42439a9db2ec74eb38d2562b070929ac6832b9c5ac"
1265
+ },
1266
+ {
1267
+ "device_id": "DEV0317",
1268
+ "fingerprint_hash": "6610a743bffff348da753cb730f657601a76f1615e1b326f24d6c41ff3764284"
1269
+ },
1270
+ {
1271
+ "device_id": "DEV0318",
1272
+ "fingerprint_hash": "7ddacc5115a19ed685f78416a488de531c1a31d25d5af726e73a1bc48888720a"
1273
+ },
1274
+ {
1275
+ "device_id": "DEV0319",
1276
+ "fingerprint_hash": "2075803ddee657b4e40349bbface85582ea4e196a6a74318edde22ae1589f971"
1277
+ },
1278
+ {
1279
+ "device_id": "DEV0320",
1280
+ "fingerprint_hash": "028383b21779f887b4485b6d12636a0f85c9702ebc751a092915ce71aa0d6ace"
1281
+ },
1282
+ {
1283
+ "device_id": "DEV0321",
1284
+ "fingerprint_hash": "19c98d16968688b36b2ba0414116092d1b934428165b5c03eef93c497c1eb253"
1285
+ },
1286
+ {
1287
+ "device_id": "DEV0322",
1288
+ "fingerprint_hash": "3baa622d9b536f0c8535c27b004a998cbfa43d5e084b030edd0ccf61d6324367"
1289
+ },
1290
+ {
1291
+ "device_id": "DEV0323",
1292
+ "fingerprint_hash": "1b957e3460a9c0b0b16976409b9899773d55316c876a4bfd54f94a83508b0c6a"
1293
+ },
1294
+ {
1295
+ "device_id": "DEV0324",
1296
+ "fingerprint_hash": "caf6aecc638f095a10c54ba7caec5ce0983d6f2807b9cbb33c0dd39631bbbfe4"
1297
+ },
1298
+ {
1299
+ "device_id": "DEV0325",
1300
+ "fingerprint_hash": "5afb7482e00a7ccc0115e20764dfe23888c834603f16e10c7995e1982b03297a"
1301
+ },
1302
+ {
1303
+ "device_id": "DEV0326",
1304
+ "fingerprint_hash": "1e0c6d91bfd58cf58a033a4b496f07ddc43a2485db53c97f53da65dacd38da7e"
1305
+ },
1306
+ {
1307
+ "device_id": "DEV0327",
1308
+ "fingerprint_hash": "155c3e55d9379fd59e4b8036799da146f8da9f5d959b86a2f597a081e7f99e5b"
1309
+ },
1310
+ {
1311
+ "device_id": "DEV0328",
1312
+ "fingerprint_hash": "963976890db37138d5c74a2010d971f7046a6c6ec4bd83e8e283196a317d13a0"
1313
+ },
1314
+ {
1315
+ "device_id": "DEV0329",
1316
+ "fingerprint_hash": "88c2e53f1ac8d07c4a6d84b17fe40c14cf01d3170fe352d11f35d2715c1e38d6"
1317
+ },
1318
+ {
1319
+ "device_id": "DEV0330",
1320
+ "fingerprint_hash": "4104f7c61aed1b5485f9a790d48f9f744f247d611e2a97726c70c59e71412c40"
1321
+ },
1322
+ {
1323
+ "device_id": "DEV0331",
1324
+ "fingerprint_hash": "3750f10aa2183a663d5bc0dc604a8c7957160f24f6cb759ed3dc119ed76c2ded"
1325
+ },
1326
+ {
1327
+ "device_id": "DEV0332",
1328
+ "fingerprint_hash": "c0287eec8dc85a43611e5eab33179dd0398c8f574c9808a2aca1c623ba730678"
1329
+ },
1330
+ {
1331
+ "device_id": "DEV0333",
1332
+ "fingerprint_hash": "8edcdd8c4b2b95f12224527ae453deda559c28d6bc4bbe3647779be590358f81"
1333
+ },
1334
+ {
1335
+ "device_id": "DEV0334",
1336
+ "fingerprint_hash": "1eb39e1659e45b94fb5edfdb90a251373ffb9cac81a92342e6d19c981beda9d6"
1337
+ },
1338
+ {
1339
+ "device_id": "DEV0335",
1340
+ "fingerprint_hash": "543b663a97bdf6de9c0073710ccb88c24633868e19568f0d5b00c1fb651d5302"
1341
+ },
1342
+ {
1343
+ "device_id": "DEV0336",
1344
+ "fingerprint_hash": "9109c0740248b973f32acb636cb0ec6945005a430ec648f1e95d134914934ce6"
1345
+ },
1346
+ {
1347
+ "device_id": "DEV0337",
1348
+ "fingerprint_hash": "43d63e5a4fd6b18db87da014f647792e182fd842f559cf3e8354363a308c18c3"
1349
+ },
1350
+ {
1351
+ "device_id": "DEV0338",
1352
+ "fingerprint_hash": "f204f3a2e9ecf217124ab56cce15a342946f9a6e3f9ddcfd84fa4dfbbc7c7973"
1353
+ },
1354
+ {
1355
+ "device_id": "DEV0339",
1356
+ "fingerprint_hash": "3e31c4e8b0041b6ea34b3e7617c5a015ac2be9c1a4fab3d318bf419a038e1f9c"
1357
+ },
1358
+ {
1359
+ "device_id": "DEV0340",
1360
+ "fingerprint_hash": "94e61c793b36386518cb7356fe45f9c9b7289d5b146758a0ff7bb2dba5b6b7d2"
1361
+ },
1362
+ {
1363
+ "device_id": "DEV0341",
1364
+ "fingerprint_hash": "43a3a440fe26b7a6e51745b8d7f769b4ed1ca2e93db963386002553adf6b1404"
1365
+ },
1366
+ {
1367
+ "device_id": "DEV0342",
1368
+ "fingerprint_hash": "84902f9d90d5def0a07bb72b4dfaf382ce3febd20ab64afdf36ec77a7ad4fe2f"
1369
+ },
1370
+ {
1371
+ "device_id": "DEV0343",
1372
+ "fingerprint_hash": "aa98a6ceff0130f6b05f87ee4804e9fcf4d6bb66973cede29e53da669094e929"
1373
+ },
1374
+ {
1375
+ "device_id": "DEV0344",
1376
+ "fingerprint_hash": "2b71471008456587b4f61190450ae6e158e1c0cec8dc3da656624c8a66e34321"
1377
+ },
1378
+ {
1379
+ "device_id": "DEV0345",
1380
+ "fingerprint_hash": "78b42c90293a0b12c938260e5ff8fc4ffdfcd6ed12d8ef73f2454265cd23374a"
1381
+ },
1382
+ {
1383
+ "device_id": "DEV0346",
1384
+ "fingerprint_hash": "fc6fc908f8756c581a80b56f98724f82b59beb5ec8a2258557c76a84d35ff415"
1385
+ },
1386
+ {
1387
+ "device_id": "DEV0347",
1388
+ "fingerprint_hash": "4a458da7e90ff787366e0b9aa81ed3cca1e0706320a9e8067318e5636afb8483"
1389
+ },
1390
+ {
1391
+ "device_id": "DEV0348",
1392
+ "fingerprint_hash": "c4cb77dbe60964f26481b11b6d2ff21f1a7cb547ea2ed4eb0f76296cff6c06a8"
1393
+ },
1394
+ {
1395
+ "device_id": "DEV0349",
1396
+ "fingerprint_hash": "e2f8acc7dfafdd1156eb71a5fa39604cf45239acf998eefed3f80c97691f51aa"
1397
+ },
1398
+ {
1399
+ "device_id": "DEV0350",
1400
+ "fingerprint_hash": "5450264019804e6b8ad7817ebb005336cdd1dc8579328461bc4c31ee71cbda0c"
1401
+ },
1402
+ {
1403
+ "device_id": "DEV0351",
1404
+ "fingerprint_hash": "f4e0e8bb056c2357822015c06b3e578f46fe71fd21300b008ab75910359977f9"
1405
+ },
1406
+ {
1407
+ "device_id": "DEV0352",
1408
+ "fingerprint_hash": "40d5cb885d8096b28dbcf5663b4653f6cda18d778ccdcc2249be3ff8ad80e774"
1409
+ },
1410
+ {
1411
+ "device_id": "DEV0353",
1412
+ "fingerprint_hash": "d2872fead2dee3cfbb27636b8ff43a5d680cf79b45c8a86ba9f29ed42d3392f5"
1413
+ },
1414
+ {
1415
+ "device_id": "DEV0354",
1416
+ "fingerprint_hash": "7fa0626b8a6cd632715ea08cbdf7219b87180ae2b0aa5801e7463fa8a357264b"
1417
+ },
1418
+ {
1419
+ "device_id": "DEV0355",
1420
+ "fingerprint_hash": "a67a03b4a7448547ecc7c80f351da5c33ab2579b89a18dd2edf087693bb07660"
1421
+ },
1422
+ {
1423
+ "device_id": "DEV0356",
1424
+ "fingerprint_hash": "2339f6d92bdc987ce2c6b5c8227d45e955f3ff4b432af62a93f523581fadb5b4"
1425
+ },
1426
+ {
1427
+ "device_id": "DEV0357",
1428
+ "fingerprint_hash": "bff83bc52c1e1e2bc655326ba138a45d2938589c687b6e0477ffbd34648348ae"
1429
+ },
1430
+ {
1431
+ "device_id": "DEV0358",
1432
+ "fingerprint_hash": "5845714fbdfb94d51c92cf6cb50ba23d031156e18169bb9a4f587a070daddc3a"
1433
+ },
1434
+ {
1435
+ "device_id": "DEV0359",
1436
+ "fingerprint_hash": "35baa8fbd894f9a68cf3f1b62b1b00fde15af33f5c68f67ae143659ec5791ec8"
1437
+ },
1438
+ {
1439
+ "device_id": "DEV0360",
1440
+ "fingerprint_hash": "4e2c02768131d5c5b5838b6db55d3bd9ddc0baaf38a5d1d39b80cd5d5298b1c6"
1441
+ },
1442
+ {
1443
+ "device_id": "DEV0361",
1444
+ "fingerprint_hash": "7ac2178859e4e6b8e302859df44c3f87124d057cf2356824dffc1b2f1c4f46a0"
1445
+ },
1446
+ {
1447
+ "device_id": "DEV0362",
1448
+ "fingerprint_hash": "7332b2d9887fd9662b3ef0aaba5f91c5e4f92a118f89fe33a22db102b3b064a1"
1449
+ },
1450
+ {
1451
+ "device_id": "DEV0363",
1452
+ "fingerprint_hash": "338b4dac3a52a78e82ed85cb58ed2fa7964028414e6ce59d32cb829f1c642b63"
1453
+ },
1454
+ {
1455
+ "device_id": "DEV0364",
1456
+ "fingerprint_hash": "af9ecf8912bdf2f7d3f5972738bfd7505f4a12fd96be6b51c14b276e711ef73b"
1457
+ },
1458
+ {
1459
+ "device_id": "DEV0365",
1460
+ "fingerprint_hash": "70f63e12b2e021c53fa7344408f82941f8dd75e797997da5f1d45b2c735a1ab2"
1461
+ },
1462
+ {
1463
+ "device_id": "DEV0366",
1464
+ "fingerprint_hash": "7e3414abd8b450355cce3b705aedc419b26e80110536ba090aa4c22807e97f1b"
1465
+ },
1466
+ {
1467
+ "device_id": "DEV0367",
1468
+ "fingerprint_hash": "db98faa388e0f26d2c3ed83ce659b9a9a1913097e87fca0e6bc05798a8e00732"
1469
+ },
1470
+ {
1471
+ "device_id": "DEV0368",
1472
+ "fingerprint_hash": "589ef556ce6deaa39c5736e143c59902e513bafd01dbaac8858a78bb868e3bf1"
1473
+ },
1474
+ {
1475
+ "device_id": "DEV0369",
1476
+ "fingerprint_hash": "c8347ad36369f57975e1ab54654b91b0be097f8f43171fd4b3955af92adfc52a"
1477
+ },
1478
+ {
1479
+ "device_id": "DEV0370",
1480
+ "fingerprint_hash": "3e8717ef1527a4d24e349e35bd0d417368f5f0eed978c059407040681c6d2049"
1481
+ },
1482
+ {
1483
+ "device_id": "DEV0371",
1484
+ "fingerprint_hash": "77af1475dd4f73383fd8b3994f50aee8be397af27e6336ea48d2d710892a4191"
1485
+ },
1486
+ {
1487
+ "device_id": "DEV0372",
1488
+ "fingerprint_hash": "249281fdc14979308cf7f9dfd9e0c0a4d8e645d0662f62c7b45a9862fa872307"
1489
+ },
1490
+ {
1491
+ "device_id": "DEV0373",
1492
+ "fingerprint_hash": "65c5181a99c0005b407171c48cc3f66972194b1e836ad6be9099808e57339cfe"
1493
+ },
1494
+ {
1495
+ "device_id": "DEV0374",
1496
+ "fingerprint_hash": "de67bcdbb80565e48324d8cb0375ce39a5c5321ed69793c2afb50abd92777d1d"
1497
+ },
1498
+ {
1499
+ "device_id": "DEV0375",
1500
+ "fingerprint_hash": "7746acef567ef899529895a1ced139997c755f86eb736a2794da1606be9e4f19"
1501
+ },
1502
+ {
1503
+ "device_id": "DEV0376",
1504
+ "fingerprint_hash": "ca101396c98d27c3a39f8b1eee04cc6be96b633379ee94d47e83ba9c0b2b4b50"
1505
+ },
1506
+ {
1507
+ "device_id": "DEV0377",
1508
+ "fingerprint_hash": "041eb73d9985dade88c032991668490af1423d5b5e7e5fa17527837bd325cb3d"
1509
+ },
1510
+ {
1511
+ "device_id": "DEV0378",
1512
+ "fingerprint_hash": "c4369fe5dbf95cce3a08997f58c30fa7907756719be669419968537de9c25034"
1513
+ },
1514
+ {
1515
+ "device_id": "DEV0379",
1516
+ "fingerprint_hash": "33aa44929db71ba8291c939b6c51714ae6b2fe8a8fbbd70edd0fdaa7f67f950e"
1517
+ },
1518
+ {
1519
+ "device_id": "DEV0380",
1520
+ "fingerprint_hash": "73b65a89e55a261792a5cc2b48a3efee649887dfcf1a5c64816b2d053b43b668"
1521
+ },
1522
+ {
1523
+ "device_id": "DEV0381",
1524
+ "fingerprint_hash": "d4cd3515bc579cd7e2e816214a71771dae33c8a15793fa3423eef304ffc0a390"
1525
+ },
1526
+ {
1527
+ "device_id": "DEV0382",
1528
+ "fingerprint_hash": "591f6f75b2cb13bf674ffec239d8948b291ce59b893594cc0e98c9e6899628e8"
1529
+ },
1530
+ {
1531
+ "device_id": "DEV0383",
1532
+ "fingerprint_hash": "03f4313de40f90ade2782f372440f368f9c324ba7c897716da633a3d6654a01b"
1533
+ },
1534
+ {
1535
+ "device_id": "DEV0384",
1536
+ "fingerprint_hash": "5bade8568d9b9a6a3d05b203c71320c939669d4d4edad8370c9047b6b33a22a3"
1537
+ },
1538
+ {
1539
+ "device_id": "DEV0385",
1540
+ "fingerprint_hash": "de3270a04c5314e5891f4f25580832255a266629e053d60212b4dd1eaf688805"
1541
+ },
1542
+ {
1543
+ "device_id": "DEV0386",
1544
+ "fingerprint_hash": "413e20233009dd16b8386706fb59418f0d9ac9c5b0ebf950cf267d07f8c90262"
1545
+ },
1546
+ {
1547
+ "device_id": "DEV0387",
1548
+ "fingerprint_hash": "8b92abd3377c7807cdca4b7d0e3cac6334e70c59000f4d58734ff1dab8e3f6bc"
1549
+ },
1550
+ {
1551
+ "device_id": "DEV0388",
1552
+ "fingerprint_hash": "10a98c8d2d202659a66009098367be3029eeb4b2863326e13105d4bebd3f85dd"
1553
+ },
1554
+ {
1555
+ "device_id": "DEV0389",
1556
+ "fingerprint_hash": "8b381105a40967c66f6fc5a1116c1547cccb7ce096426bd98e7133b2bbd74d98"
1557
+ },
1558
+ {
1559
+ "device_id": "DEV0390",
1560
+ "fingerprint_hash": "bc050715f180192af101f3b2bbeee265a4389caa222ba4d12492c30071b3a7d7"
1561
+ },
1562
+ {
1563
+ "device_id": "DEV0391",
1564
+ "fingerprint_hash": "eb804ba0eb6894c046b9ebf127509962445ca99e7f1c1fc3b428c9878522c229"
1565
+ },
1566
+ {
1567
+ "device_id": "DEV0392",
1568
+ "fingerprint_hash": "b2c34f0585853455e1a407a438063d959eda2a7798235bbb98250fd3b01975f9"
1569
+ },
1570
+ {
1571
+ "device_id": "DEV0393",
1572
+ "fingerprint_hash": "6a25c786986c447728e40018ec6cddd7d175592b74d9b73b224a1d3b9aa386d9"
1573
+ },
1574
+ {
1575
+ "device_id": "DEV0394",
1576
+ "fingerprint_hash": "37df7aca885d8db022aae3ac94a852453f36349b4422ea16ace306ad322a4f07"
1577
+ },
1578
+ {
1579
+ "device_id": "DEV0395",
1580
+ "fingerprint_hash": "7526569717ebb9132f7304460c53ab0103dc97be83724f75fd34bf020d0914d2"
1581
+ },
1582
+ {
1583
+ "device_id": "DEV0396",
1584
+ "fingerprint_hash": "695c18166bbb89e295302f4fb2456ed01797f322eed553e104f16177105d5879"
1585
+ },
1586
+ {
1587
+ "device_id": "DEV0397",
1588
+ "fingerprint_hash": "59e61fd370f004972f61f8b04291f049181f6d8c6abff2129fb178014569a2b8"
1589
+ },
1590
+ {
1591
+ "device_id": "DEV0398",
1592
+ "fingerprint_hash": "7f4a565f82d58b522a0c1a3f69ff63d43fa623d5a7e51b1de9b0a88daaf4c88d"
1593
+ },
1594
+ {
1595
+ "device_id": "DEV0399",
1596
+ "fingerprint_hash": "f9e9fb1405f2be65698e4dee8f93ef47044f0b62036162203cdce8779057a0fa"
1597
+ },
1598
+ {
1599
+ "device_id": "DEV0400",
1600
+ "fingerprint_hash": "bafc6f592c73bd6b395fcb2c13307d2052db7ba2db2002cf2c070503db0acc3c"
1601
+ },
1602
+ {
1603
+ "device_id": "DEV0401",
1604
+ "fingerprint_hash": "83c011bac645616b7a37e03d837e8b414927d82301adecb82d65dd8357238c41"
1605
+ },
1606
+ {
1607
+ "device_id": "DEV0402",
1608
+ "fingerprint_hash": "3301232dc5d5e575c4ba913a642a50643788c5879005bc4ac4186ac4c923c0f9"
1609
+ },
1610
+ {
1611
+ "device_id": "DEV0403",
1612
+ "fingerprint_hash": "80cd90df12a54dc42a90bb732c572ce24474c839db1a648aaa677645686a6efe"
1613
+ },
1614
+ {
1615
+ "device_id": "DEV0404",
1616
+ "fingerprint_hash": "7f958138868e5dc8bbcc5200af59d09008de909709b51cffdf38d36610be358c"
1617
+ },
1618
+ {
1619
+ "device_id": "DEV0405",
1620
+ "fingerprint_hash": "61d5a1c86a53a9bcd943fd3ae9c3a0ff8d818e7d8d9b0d9541080a1a10c0db51"
1621
+ }
1622
+ ]
data/synthetic/ground_truth.json ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "rings": [
3
+ {
4
+ "ring_id": "RING01",
5
+ "customer_ids": [
6
+ "CUST0001",
7
+ "CUST0002",
8
+ "CUST0003"
9
+ ],
10
+ "type": "fraud"
11
+ },
12
+ {
13
+ "ring_id": "RING02",
14
+ "customer_ids": [
15
+ "CUST0004",
16
+ "CUST0005",
17
+ "CUST0006",
18
+ "CUST0007",
19
+ "CUST0008"
20
+ ],
21
+ "type": "fraud"
22
+ },
23
+ {
24
+ "ring_id": "RING03",
25
+ "customer_ids": [
26
+ "CUST0009",
27
+ "CUST0010",
28
+ "CUST0011",
29
+ "CUST0012",
30
+ "CUST0013"
31
+ ],
32
+ "type": "fraud"
33
+ },
34
+ {
35
+ "ring_id": "RING04",
36
+ "customer_ids": [
37
+ "CUST0014",
38
+ "CUST0015",
39
+ "CUST0016",
40
+ "CUST0017",
41
+ "CUST0018"
42
+ ],
43
+ "type": "fraud"
44
+ },
45
+ {
46
+ "ring_id": "RING05",
47
+ "customer_ids": [
48
+ "CUST0019",
49
+ "CUST0020",
50
+ "CUST0021",
51
+ "CUST0022",
52
+ "CUST0023"
53
+ ],
54
+ "type": "fraud"
55
+ },
56
+ {
57
+ "ring_id": "RING06",
58
+ "customer_ids": [
59
+ "CUST0024",
60
+ "CUST0025",
61
+ "CUST0026",
62
+ "CUST0027",
63
+ "CUST0028",
64
+ "CUST0029"
65
+ ],
66
+ "type": "fraud"
67
+ },
68
+ {
69
+ "ring_id": "RING07",
70
+ "customer_ids": [
71
+ "CUST0030",
72
+ "CUST0031",
73
+ "CUST0032"
74
+ ],
75
+ "type": "fraud"
76
+ },
77
+ {
78
+ "ring_id": "RING08",
79
+ "customer_ids": [
80
+ "CUST0033",
81
+ "CUST0034",
82
+ "CUST0035"
83
+ ],
84
+ "type": "fraud"
85
+ },
86
+ {
87
+ "ring_id": "RING09",
88
+ "customer_ids": [
89
+ "CUST0036",
90
+ "CUST0037",
91
+ "CUST0038",
92
+ "CUST0039",
93
+ "CUST0040"
94
+ ],
95
+ "type": "fraud"
96
+ },
97
+ {
98
+ "ring_id": "RING10",
99
+ "customer_ids": [
100
+ "CUST0041",
101
+ "CUST0042",
102
+ "CUST0043",
103
+ "CUST0044"
104
+ ],
105
+ "type": "fraud"
106
+ },
107
+ {
108
+ "ring_id": "RING11",
109
+ "customer_ids": [
110
+ "CUST0501",
111
+ "CUST0502",
112
+ "CUST0503"
113
+ ],
114
+ "type": "fraud"
115
+ }
116
+ ],
117
+ "stress_clusters": [
118
+ {
119
+ "cluster_id": "STRESS01",
120
+ "customer_ids": [
121
+ "CUST0045",
122
+ "CUST0046",
123
+ "CUST0047",
124
+ "CUST0048"
125
+ ],
126
+ "type": "legitimate_shared_address"
127
+ },
128
+ {
129
+ "cluster_id": "STRESS02",
130
+ "customer_ids": [
131
+ "CUST0049",
132
+ "CUST0050",
133
+ "CUST0051",
134
+ "CUST0052"
135
+ ],
136
+ "type": "legitimate_shared_address"
137
+ },
138
+ {
139
+ "cluster_id": "STRESS03",
140
+ "customer_ids": [
141
+ "CUST0053",
142
+ "CUST0054",
143
+ "CUST0055",
144
+ "CUST0056"
145
+ ],
146
+ "type": "legitimate_shared_address"
147
+ },
148
+ {
149
+ "cluster_id": "STRESS04",
150
+ "customer_ids": [
151
+ "CUST0057",
152
+ "CUST0058"
153
+ ],
154
+ "type": "legitimate_shared_address"
155
+ },
156
+ {
157
+ "cluster_id": "STRESS05",
158
+ "customer_ids": [
159
+ "CUST0059",
160
+ "CUST0060"
161
+ ],
162
+ "type": "legitimate_shared_address"
163
+ }
164
+ ]
165
+ }
data/synthetic/orders.json ADDED
The diff for this file is too large to render. See raw diff
 
data/synthetic/payment_instruments.json ADDED
@@ -0,0 +1,1932 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "instrument_id": "PAY0001",
4
+ "type": "upi",
5
+ "last4_hash": "43854d169fe9af1d004773b3c9822b3f57b140d4703096704f3d795b1581706b"
6
+ },
7
+ {
8
+ "instrument_id": "PAY0002",
9
+ "type": "card",
10
+ "last4_hash": "9b3cea3bd8789ba47cc969bff5914da7ebac8e0052e282917299814a53a7dfdb"
11
+ },
12
+ {
13
+ "instrument_id": "PAY0003",
14
+ "type": "upi",
15
+ "last4_hash": "72cc443db576409f0e56de30286baede30ae7c2d0f6e3e49f3fc5ac7e9d5b276"
16
+ },
17
+ {
18
+ "instrument_id": "PAY0004",
19
+ "type": "upi",
20
+ "last4_hash": "b324329805a238135ed3f429bde8cbe869b2b2190e17edccd3ae9e036565a7ae"
21
+ },
22
+ {
23
+ "instrument_id": "PAY0005",
24
+ "type": "upi",
25
+ "last4_hash": "ba2854ea732fe9c5c45bb5532172ea531a8510cd03fde9247f0d1f38368e49f1"
26
+ },
27
+ {
28
+ "instrument_id": "PAY0006",
29
+ "type": "upi",
30
+ "last4_hash": "1314e83b4d4e34a1bcdb5665aedc31616d49cef471cca9561c471abd798b0f5a"
31
+ },
32
+ {
33
+ "instrument_id": "PAY0007",
34
+ "type": "card",
35
+ "last4_hash": "b39b7ba7e07d710447e89c40277171aa4c568e3de08e4970d7579288af5bf3f4"
36
+ },
37
+ {
38
+ "instrument_id": "PAY0008",
39
+ "type": "card",
40
+ "last4_hash": "9199d9e5b275563cd8c867aa6013d32117ad6232cd0d6e0b7e28b9eb54c80b2c"
41
+ },
42
+ {
43
+ "instrument_id": "PAY0009",
44
+ "type": "card",
45
+ "last4_hash": "48ccfd09d04c36762f6b0c83bf32f695caa2f2dcf773703acdb4a030a3fa1dc0"
46
+ },
47
+ {
48
+ "instrument_id": "PAY0010",
49
+ "type": "card",
50
+ "last4_hash": "c5067bcbc573d35e3788d098a8d58772fc5717e120b4990f8bcbfd634e45853a"
51
+ },
52
+ {
53
+ "instrument_id": "PAY0011",
54
+ "type": "card",
55
+ "last4_hash": "39e71545d840848a9af1a669d7e6aabf8ee95536a2f219820c450aa31f796573"
56
+ },
57
+ {
58
+ "instrument_id": "PAY0012",
59
+ "type": "upi",
60
+ "last4_hash": "fdb37acb269dac7997162fd98b93c16bc8bdaa2906747082415d809cf1df6be5"
61
+ },
62
+ {
63
+ "instrument_id": "PAY0013",
64
+ "type": "card",
65
+ "last4_hash": "44544e0d6770664c4c13ae2ed2438373c2014f112a6f5fcd7c241ba79222bc16"
66
+ },
67
+ {
68
+ "instrument_id": "PAY0014",
69
+ "type": "upi",
70
+ "last4_hash": "4765a7b39436675afd5fdff5b56b41db91c05ecb524a2a7def0318b3d5de307c"
71
+ },
72
+ {
73
+ "instrument_id": "PAY0015",
74
+ "type": "card",
75
+ "last4_hash": "ed46f81f54d1ac6c2586e0003e659bc0595f7ebfd6fa37dd5b354c56451d4ab2"
76
+ },
77
+ {
78
+ "instrument_id": "PAY0016",
79
+ "type": "upi",
80
+ "last4_hash": "2d56bccc2c06064b1cc7bd9a8605692e6c708145159d69b6439d53d5594386b1"
81
+ },
82
+ {
83
+ "instrument_id": "PAY0017",
84
+ "type": "upi",
85
+ "last4_hash": "29e3061bd659a3a4b6ac671fd1dc8f7ff9f453aa6caaf15289b9a722abd03b7c"
86
+ },
87
+ {
88
+ "instrument_id": "PAY0018",
89
+ "type": "upi",
90
+ "last4_hash": "b2ce2a829d3d50ba8e38eac676598731979f3a8b019e4f247fd85828642ee9f8"
91
+ },
92
+ {
93
+ "instrument_id": "PAY0019",
94
+ "type": "upi",
95
+ "last4_hash": "0941be7a3eff242affd46525c1c0468acef945d064fe8c0b553d14e0074303a2"
96
+ },
97
+ {
98
+ "instrument_id": "PAY0020",
99
+ "type": "upi",
100
+ "last4_hash": "3769b2ecd28cdf0ed1d25aad16979ce2a3ec7782017c61941e8b161c44baba08"
101
+ },
102
+ {
103
+ "instrument_id": "PAY0021",
104
+ "type": "card",
105
+ "last4_hash": "ab86fe44dea15e7a6b438f741aa208f079d55f626f5c471c999b2ed78b37bd6a"
106
+ },
107
+ {
108
+ "instrument_id": "PAY0022",
109
+ "type": "upi",
110
+ "last4_hash": "656ec82405d95b39a019289db15f781b4f8be7824060ab938656199fce05961f"
111
+ },
112
+ {
113
+ "instrument_id": "PAY0023",
114
+ "type": "upi",
115
+ "last4_hash": "cf5b255ef873089b5efa6efa80f98b50cb5df021246f561771e50fa90ff34c78"
116
+ },
117
+ {
118
+ "instrument_id": "PAY0024",
119
+ "type": "card",
120
+ "last4_hash": "faede57cfab74b18e63014bf3295fde357982f05d67e6dac2a0b28b333b5c396"
121
+ },
122
+ {
123
+ "instrument_id": "PAY0025",
124
+ "type": "upi",
125
+ "last4_hash": "41e20f4fbbd493f6ee8744ec2ac8143aba5d3cb34eec0338a051e460fed85707"
126
+ },
127
+ {
128
+ "instrument_id": "PAY0026",
129
+ "type": "card",
130
+ "last4_hash": "7808b86edb7b1acd97860de5b0014865ec15ea7a2f6e8ba5c0a3c88127a9447b"
131
+ },
132
+ {
133
+ "instrument_id": "PAY0027",
134
+ "type": "upi",
135
+ "last4_hash": "ff293a64aa18c6df0364f0a6bea775dcb81d5d5cc4fce958612ecd70eeb64aee"
136
+ },
137
+ {
138
+ "instrument_id": "PAY0028",
139
+ "type": "card",
140
+ "last4_hash": "27995993191887fe707d8f4be3a3b3e8190b12dc0ba20cc441ded243f8a304a4"
141
+ },
142
+ {
143
+ "instrument_id": "PAY0029",
144
+ "type": "upi",
145
+ "last4_hash": "d8f34a8a519c35fd11b63a97cb68ab840d9993ea4e09f25ba4cbf20fddade301"
146
+ },
147
+ {
148
+ "instrument_id": "PAY0030",
149
+ "type": "upi",
150
+ "last4_hash": "ab8ba612699a1b34ab8c4398b833d221f4bfe4e43c188f8c571de33225790419"
151
+ },
152
+ {
153
+ "instrument_id": "PAY0031",
154
+ "type": "upi",
155
+ "last4_hash": "382a90367a19bf8ef8631798e4d29596d729604fe59d6c6d439702d5ffe87970"
156
+ },
157
+ {
158
+ "instrument_id": "PAY0032",
159
+ "type": "upi",
160
+ "last4_hash": "be53a1caecfd70a4ca27be2289e8c54be3f6af7f9bf67ef45af4ff157e63bf5f"
161
+ },
162
+ {
163
+ "instrument_id": "PAY0033",
164
+ "type": "card",
165
+ "last4_hash": "b23d8b6a24247560231f60bd114e288d0ac8707610ff3c53bf82897eace08854"
166
+ },
167
+ {
168
+ "instrument_id": "PAY0034",
169
+ "type": "upi",
170
+ "last4_hash": "be08d86973638e7039b4265f4cc42328e0a99933baf066ed7c252a2594c25282"
171
+ },
172
+ {
173
+ "instrument_id": "PAY0035",
174
+ "type": "card",
175
+ "last4_hash": "c81b352b270b2e498256b5e24c726a039cd665477a83d71d95633340782782d4"
176
+ },
177
+ {
178
+ "instrument_id": "PAY0036",
179
+ "type": "upi",
180
+ "last4_hash": "9881a093db6601fd80ff7825844a119d17b55cde13322cc5b3f8e57a28d9c701"
181
+ },
182
+ {
183
+ "instrument_id": "PAY0037",
184
+ "type": "upi",
185
+ "last4_hash": "4537f0f8c9dc0d35adf0338d454e2d324bcd289713b434c7d29708c06a3b5dd7"
186
+ },
187
+ {
188
+ "instrument_id": "PAY0038",
189
+ "type": "card",
190
+ "last4_hash": "39d5ce6249548c318a5f0b6bf0a3e2f234750a9741b6ce2f9eff8670bc7cd7af"
191
+ },
192
+ {
193
+ "instrument_id": "PAY0039",
194
+ "type": "upi",
195
+ "last4_hash": "9f7599e78fd8ce9803dd88d94f75372f3d02c861b7d4b8b6a14144a3eb7d15d0"
196
+ },
197
+ {
198
+ "instrument_id": "PAY0040",
199
+ "type": "card",
200
+ "last4_hash": "cf7dee3443a859943d9ecf558fe5cf260f9acb4150b5d18d8b2e04be1cc3ca54"
201
+ },
202
+ {
203
+ "instrument_id": "PAY0041",
204
+ "type": "card",
205
+ "last4_hash": "55fdec963805de594b61b2c1692cadc2c1dfb844d6ac10c5bfed33c842087b2e"
206
+ },
207
+ {
208
+ "instrument_id": "PAY0042",
209
+ "type": "card",
210
+ "last4_hash": "03afa3478417ff5086b7560e07ccafaeb73f2772ce4e05fecde39fcd32a5437e"
211
+ },
212
+ {
213
+ "instrument_id": "PAY0043",
214
+ "type": "upi",
215
+ "last4_hash": "d934819258879e09373979e6d1fb0d5da3404039738bd5d806df0eca450a3429"
216
+ },
217
+ {
218
+ "instrument_id": "PAY0044",
219
+ "type": "card",
220
+ "last4_hash": "48f84ea6043f5313382811d79ec2ba3d163c943b0e33ca3fe2ba2e9a4f18e935"
221
+ },
222
+ {
223
+ "instrument_id": "PAY0045",
224
+ "type": "upi",
225
+ "last4_hash": "37a5eba54aa89b51ef3f6bb7615f043ac56587d4d67e10fb85e1ec42495a41ab"
226
+ },
227
+ {
228
+ "instrument_id": "PAY0046",
229
+ "type": "card",
230
+ "last4_hash": "a11f793d5f24841552b2befc8d946d9f7e5652edfb58bd49c3a8423a6031c952"
231
+ },
232
+ {
233
+ "instrument_id": "PAY0047",
234
+ "type": "upi",
235
+ "last4_hash": "140a85d1fd4c514a914643ec2922ce6818ae675e73e2eef6e641fee467438ed2"
236
+ },
237
+ {
238
+ "instrument_id": "PAY0048",
239
+ "type": "upi",
240
+ "last4_hash": "4b734774fc6f30af3315238e013de6354eeff142ce27efdbc72705e3a13f3d5f"
241
+ },
242
+ {
243
+ "instrument_id": "PAY0049",
244
+ "type": "card",
245
+ "last4_hash": "0bb96d87216f5159d997d566969931579a74f4fcd81e9e4280e5354a4ba0c2fd"
246
+ },
247
+ {
248
+ "instrument_id": "PAY0050",
249
+ "type": "card",
250
+ "last4_hash": "5e27752e101ec179e101f6016d7b015f2a408795a3135953ef6066f73753fadd"
251
+ },
252
+ {
253
+ "instrument_id": "PAY0051",
254
+ "type": "upi",
255
+ "last4_hash": "89c55097f7602585a95ada5ee3a306ecb4966744a2586946b15e978fb46f6832"
256
+ },
257
+ {
258
+ "instrument_id": "PAY0052",
259
+ "type": "card",
260
+ "last4_hash": "741f514bbddf7310b4d0fe0dea7f062941dd67b8090b66d2bcf6bc621bbd2021"
261
+ },
262
+ {
263
+ "instrument_id": "PAY0053",
264
+ "type": "upi",
265
+ "last4_hash": "eaf52ea64d24cb70e3cb3e2790c8cead55c6bd523b0b0de6cd0dbad4cae51c6e"
266
+ },
267
+ {
268
+ "instrument_id": "PAY0054",
269
+ "type": "card",
270
+ "last4_hash": "cc55b4c2e7e6d5ef1642141e8a25944cfa6d7ed2b8e8385adcc92c6bb3aee571"
271
+ },
272
+ {
273
+ "instrument_id": "PAY0055",
274
+ "type": "card",
275
+ "last4_hash": "1f5e12a1dcc43dfd45e6709479e6cc62b1a6426a6ff78926166808e13a813c9d"
276
+ },
277
+ {
278
+ "instrument_id": "PAY0056",
279
+ "type": "card",
280
+ "last4_hash": "900cb8e36b717b81b9dbbb51cfae997cf5236c36afea5ba7100d005ad52d1cbc"
281
+ },
282
+ {
283
+ "instrument_id": "PAY0057",
284
+ "type": "upi",
285
+ "last4_hash": "a6781ca9dc05eb6d4a0cd863aa22a558554373cfcd2be1242f8df005f98416d6"
286
+ },
287
+ {
288
+ "instrument_id": "PAY0058",
289
+ "type": "upi",
290
+ "last4_hash": "3a14d640f42889d144e71bda4425b340e0b1f62d87f6d9bffd464598655d43c1"
291
+ },
292
+ {
293
+ "instrument_id": "PAY0059",
294
+ "type": "upi",
295
+ "last4_hash": "cec0db12ddf9f2d2e372eca85188235f332465f82e371202f3950d0b77fb5b6c"
296
+ },
297
+ {
298
+ "instrument_id": "PAY0060",
299
+ "type": "upi",
300
+ "last4_hash": "c779fc361251ad4c3db1fd8655b96bfd565eff7f13a414ace30eda2f89c9cd45"
301
+ },
302
+ {
303
+ "instrument_id": "PAY0061",
304
+ "type": "card",
305
+ "last4_hash": "07007ba87ea78903e0ec57160cac3ff7dfc1109f2d84cc6eed9ffbd8358a62d1"
306
+ },
307
+ {
308
+ "instrument_id": "PAY0062",
309
+ "type": "upi",
310
+ "last4_hash": "f1980fc5343d10c28baa1ce65de484c08733c23d7e43977d53aec202775ca81e"
311
+ },
312
+ {
313
+ "instrument_id": "PAY0063",
314
+ "type": "upi",
315
+ "last4_hash": "4ef51169834cb29778aa564dd2dd9ebc6017656e2d47f5a21c689a44798c1605"
316
+ },
317
+ {
318
+ "instrument_id": "PAY0064",
319
+ "type": "card",
320
+ "last4_hash": "9302a967cce443780477212553ae3d74625dd165a096a4ee8299dcf0c8079866"
321
+ },
322
+ {
323
+ "instrument_id": "PAY0065",
324
+ "type": "upi",
325
+ "last4_hash": "86dfdb71e782a8cec375d222d5383cc95e042aad436f9fa3a47f1d7c8c3864f1"
326
+ },
327
+ {
328
+ "instrument_id": "PAY0066",
329
+ "type": "card",
330
+ "last4_hash": "ef3eaba74dcc1c3e6536e12d4a95d94fe3d32b29f1d217ce6b377049efe60511"
331
+ },
332
+ {
333
+ "instrument_id": "PAY0067",
334
+ "type": "upi",
335
+ "last4_hash": "7f58b9425094d6f95c8f177eb93b12c1701a63978f9bb291335345d852425c90"
336
+ },
337
+ {
338
+ "instrument_id": "PAY0068",
339
+ "type": "card",
340
+ "last4_hash": "45b120bb08854e18123188f37c3d0b52d06781d415fb5b56a684870a779e09bc"
341
+ },
342
+ {
343
+ "instrument_id": "PAY0069",
344
+ "type": "card",
345
+ "last4_hash": "43a70ba6b21894cd24165d9d7ee59b012e698cf97e22fd275ccdd05a556e7061"
346
+ },
347
+ {
348
+ "instrument_id": "PAY0070",
349
+ "type": "upi",
350
+ "last4_hash": "22e8922403c6ca57a6a7eb282337498418fa5f89ac9110534278aef3736aa28c"
351
+ },
352
+ {
353
+ "instrument_id": "PAY0071",
354
+ "type": "upi",
355
+ "last4_hash": "7a9e521df4c6ba4d71bb4bad29f1f771918ffa0b507d9a6381f642b6dfa4f6d3"
356
+ },
357
+ {
358
+ "instrument_id": "PAY0072",
359
+ "type": "card",
360
+ "last4_hash": "f35bdfbc3b7e089ee8a84c2684deb1f5c96732895d79086eb8fa0458742a469d"
361
+ },
362
+ {
363
+ "instrument_id": "PAY0073",
364
+ "type": "card",
365
+ "last4_hash": "e26f3716adbe1a42e8b784b9d6670f922151428235cd146c3cc7b80653b2f9d7"
366
+ },
367
+ {
368
+ "instrument_id": "PAY0074",
369
+ "type": "upi",
370
+ "last4_hash": "dff532682c09d2c12af80c41a395f9b853e8edae6ae0bb129fd9dc7274d52b63"
371
+ },
372
+ {
373
+ "instrument_id": "PAY0075",
374
+ "type": "card",
375
+ "last4_hash": "4817ddb54d7a527a5cb1e069434fdfb5350bfdc910cc6a3aa35b54a4036b9809"
376
+ },
377
+ {
378
+ "instrument_id": "PAY0076",
379
+ "type": "card",
380
+ "last4_hash": "58e47bf2ec5f0d27dab6b42787a65ea8cb3a2c3c88816dbc2eee8bc20d600a1a"
381
+ },
382
+ {
383
+ "instrument_id": "PAY0077",
384
+ "type": "upi",
385
+ "last4_hash": "e7635b6f96d7470ea28f65a202cd9e0c7abf789ee12f1ef5b9b80fe5f1bae7d2"
386
+ },
387
+ {
388
+ "instrument_id": "PAY0078",
389
+ "type": "upi",
390
+ "last4_hash": "6692ae362de80b8769f3a4b190b025dfdf3ba7b184d2928d35c9c80061ff5348"
391
+ },
392
+ {
393
+ "instrument_id": "PAY0079",
394
+ "type": "upi",
395
+ "last4_hash": "399bd15402e3c3ea9a728f19361e02c634c9a46b40051212832d8817d830aa5e"
396
+ },
397
+ {
398
+ "instrument_id": "PAY0080",
399
+ "type": "upi",
400
+ "last4_hash": "3ee5cc0469c7e67513b98291b35b41cbd00f5c5e20973b783b2b3b44b225a81c"
401
+ },
402
+ {
403
+ "instrument_id": "PAY0081",
404
+ "type": "card",
405
+ "last4_hash": "e753559bf52404ed6877db546a80e189e22c6ff1ca965d7115a882d4335c9be7"
406
+ },
407
+ {
408
+ "instrument_id": "PAY0082",
409
+ "type": "upi",
410
+ "last4_hash": "ab3c34aaa228b75bdfa29314f3c134dacadede2deee1dfdb3fda46cf3628e178"
411
+ },
412
+ {
413
+ "instrument_id": "PAY0083",
414
+ "type": "upi",
415
+ "last4_hash": "d0fc42ab00f8e3e251e0418898559ee941b0de10f08f170bd11bf51757b270bd"
416
+ },
417
+ {
418
+ "instrument_id": "PAY0084",
419
+ "type": "card",
420
+ "last4_hash": "e18f50079034b56f8d86d0e2fcaf15a6685ef6b8771711ff7bda1f937f4b10f4"
421
+ },
422
+ {
423
+ "instrument_id": "PAY0085",
424
+ "type": "upi",
425
+ "last4_hash": "e8df2d2fe1fbb87bcf45505fb1dea35d09a702ef3af8625ebf5a39830c45d1a9"
426
+ },
427
+ {
428
+ "instrument_id": "PAY0086",
429
+ "type": "card",
430
+ "last4_hash": "05e5ddbcc59eb669a2e5987e36a40537fcdb275a87b38023f9da6d41f0a5c37b"
431
+ },
432
+ {
433
+ "instrument_id": "PAY0087",
434
+ "type": "upi",
435
+ "last4_hash": "a5613be89b56ff92cc0d12ca5b2900ff0a503c9a61bc731d9a0f3c00f7bdeaa0"
436
+ },
437
+ {
438
+ "instrument_id": "PAY0088",
439
+ "type": "upi",
440
+ "last4_hash": "1c313ae9b416b8c468f7079bca03b08c0273987a41024976176bd1992399a4fa"
441
+ },
442
+ {
443
+ "instrument_id": "PAY0089",
444
+ "type": "upi",
445
+ "last4_hash": "aa0363dfc3a149e6665dff0b628546f766d3ea2910ec17078c4968c97102a9a2"
446
+ },
447
+ {
448
+ "instrument_id": "PAY0090",
449
+ "type": "card",
450
+ "last4_hash": "35a6c93873ac10cb4386cd7e3b071daa8b3002025a30284d26b3d5a0950f7900"
451
+ },
452
+ {
453
+ "instrument_id": "PAY0091",
454
+ "type": "card",
455
+ "last4_hash": "d308ddc557e774187ad6c7da81b39db990def9a39e6c88e79cb163b6bdd6e8e5"
456
+ },
457
+ {
458
+ "instrument_id": "PAY0092",
459
+ "type": "upi",
460
+ "last4_hash": "f6a7b1da20dc935df8ad86e6d517983f6f08a9937ebedd49046d209630cf5307"
461
+ },
462
+ {
463
+ "instrument_id": "PAY0093",
464
+ "type": "upi",
465
+ "last4_hash": "48ba0d3f602388d95de0e7a95c21a34daa9438ed5eb6617619728482e2da6c30"
466
+ },
467
+ {
468
+ "instrument_id": "PAY0094",
469
+ "type": "card",
470
+ "last4_hash": "97d2244a7046f7586a09c23c5c270591dfc00dbadcbe8a279d045825bd669d29"
471
+ },
472
+ {
473
+ "instrument_id": "PAY0095",
474
+ "type": "upi",
475
+ "last4_hash": "8dc3b1593caf9cd87427a1ecebe50a2c9e8a52759900cfe3358f3a55c5400bcf"
476
+ },
477
+ {
478
+ "instrument_id": "PAY0096",
479
+ "type": "upi",
480
+ "last4_hash": "899c2819e671e9241ca393e13a3b08a70d6e6c0ecad7faf5cf22999e648577aa"
481
+ },
482
+ {
483
+ "instrument_id": "PAY0097",
484
+ "type": "card",
485
+ "last4_hash": "c9916ca5880d11008999dc266d47657e927fde740bb899caa52ca5528da796b2"
486
+ },
487
+ {
488
+ "instrument_id": "PAY0098",
489
+ "type": "card",
490
+ "last4_hash": "5f9e3d4a0fa6f44a726c146ead2a301eb4026ae67b06cc5971210e5d749762b0"
491
+ },
492
+ {
493
+ "instrument_id": "PAY0099",
494
+ "type": "card",
495
+ "last4_hash": "99eeb44d3313dca5755437fca4cb7f50ccb1e9f7279abd334045d05a001fd8b8"
496
+ },
497
+ {
498
+ "instrument_id": "PAY0100",
499
+ "type": "card",
500
+ "last4_hash": "ecf8103662b93ccc41bc42ba074379fedce3688c49093102b9200f0cec473559"
501
+ },
502
+ {
503
+ "instrument_id": "PAY0101",
504
+ "type": "upi",
505
+ "last4_hash": "624ea67219372de3ff819cad5c6feb061f1f719eda1f44f94d154bf758cfd97e"
506
+ },
507
+ {
508
+ "instrument_id": "PAY0102",
509
+ "type": "card",
510
+ "last4_hash": "8c8699214133b6332adc864763533eb8880c1e6f6a7787a962cd3f5ed3e72026"
511
+ },
512
+ {
513
+ "instrument_id": "PAY0103",
514
+ "type": "card",
515
+ "last4_hash": "2e1943902be93c87e9d31812ef887d1f91326e8e8e3779feb59c48c9ac32f58f"
516
+ },
517
+ {
518
+ "instrument_id": "PAY0104",
519
+ "type": "upi",
520
+ "last4_hash": "facf1b4df937f097837b3f532e2b5dae513e76a448557385a979faa12bebef01"
521
+ },
522
+ {
523
+ "instrument_id": "PAY0105",
524
+ "type": "upi",
525
+ "last4_hash": "36d4f0701702ed6e66256c81f32544275aec9155741661d2cdad2b83525b1129"
526
+ },
527
+ {
528
+ "instrument_id": "PAY0106",
529
+ "type": "upi",
530
+ "last4_hash": "b861be6fb870d16d203b8521a1805a974d4e0503c67120b44c4c9df5778f8f12"
531
+ },
532
+ {
533
+ "instrument_id": "PAY0107",
534
+ "type": "card",
535
+ "last4_hash": "fef8ef07b0461c95476dd7b995f3333578ae82031501d300354f2263cf1a73cb"
536
+ },
537
+ {
538
+ "instrument_id": "PAY0108",
539
+ "type": "upi",
540
+ "last4_hash": "1f8509908e8e7d16fe0c0de8ea7cc0fbadb2d3c24f831ab4ac7b35ee85f09f49"
541
+ },
542
+ {
543
+ "instrument_id": "PAY0109",
544
+ "type": "upi",
545
+ "last4_hash": "7baf56a8601326f4d3b8fb1a73b013a596f37175d60075a2ba2d0cd45735ad2f"
546
+ },
547
+ {
548
+ "instrument_id": "PAY0110",
549
+ "type": "upi",
550
+ "last4_hash": "09509b09bdfa083ce661063988b5fd1fb9cb92a37c341a67355eecd6c6a778dc"
551
+ },
552
+ {
553
+ "instrument_id": "PAY0111",
554
+ "type": "upi",
555
+ "last4_hash": "ab9a89784cfae1151df7fa3057f7c20b90374c0b338fefabda9fb5954db70060"
556
+ },
557
+ {
558
+ "instrument_id": "PAY0112",
559
+ "type": "upi",
560
+ "last4_hash": "ba7b98b0c987d50c24b7851e96ba961b0f6561ba32e669918f2203ac26035829"
561
+ },
562
+ {
563
+ "instrument_id": "PAY0113",
564
+ "type": "upi",
565
+ "last4_hash": "b0e75b9a16227b9d288a465d0de38135405a7e4f0529545d7e26a4fa4c366728"
566
+ },
567
+ {
568
+ "instrument_id": "PAY0114",
569
+ "type": "card",
570
+ "last4_hash": "e68259a977d847c9ed26fc47c6855021c9a51eac28a63f9a0c012f7bbac0b556"
571
+ },
572
+ {
573
+ "instrument_id": "PAY0115",
574
+ "type": "card",
575
+ "last4_hash": "b789f4e82d98e206cf0f6cc237eff16955db29bee865372ac12af8e0226b7607"
576
+ },
577
+ {
578
+ "instrument_id": "PAY0116",
579
+ "type": "card",
580
+ "last4_hash": "b8b2994c7b8b89680041f24fbb0d10dc76e072cbefb1a342fb3d7cf4b002bec6"
581
+ },
582
+ {
583
+ "instrument_id": "PAY0117",
584
+ "type": "card",
585
+ "last4_hash": "70e8d52a9b4616e728112408ceca7abd9507e93f30e958ecb09f6ea607d99d06"
586
+ },
587
+ {
588
+ "instrument_id": "PAY0118",
589
+ "type": "card",
590
+ "last4_hash": "5b33003a928495b97792ac286d477b54dd20eb773c74ae2fb3653bc5950ad6dd"
591
+ },
592
+ {
593
+ "instrument_id": "PAY0119",
594
+ "type": "card",
595
+ "last4_hash": "173deaaf2f52bf79f028106fdbb995142cd7d77fc8b15cfa2a82d93c13f76f3c"
596
+ },
597
+ {
598
+ "instrument_id": "PAY0120",
599
+ "type": "card",
600
+ "last4_hash": "80ae95e050f70cddb9725d71e8fa265f27ecb95db2613461fd6d1c085f2dab0c"
601
+ },
602
+ {
603
+ "instrument_id": "PAY0121",
604
+ "type": "card",
605
+ "last4_hash": "e12868d1f3963e0e1129b3a6442b6d3d7d8e4eb7e27d34348b29523f0ae37748"
606
+ },
607
+ {
608
+ "instrument_id": "PAY0122",
609
+ "type": "upi",
610
+ "last4_hash": "d21c586a53e9762ebe64b80b124b6b6f00e3549ec304f6d4aa8d0c65c02e8704"
611
+ },
612
+ {
613
+ "instrument_id": "PAY0123",
614
+ "type": "card",
615
+ "last4_hash": "2c6b7bf2a4ef2fc87ce92b7097c6c477825b898877f3c7406c9609f7e5ddb8e6"
616
+ },
617
+ {
618
+ "instrument_id": "PAY0124",
619
+ "type": "upi",
620
+ "last4_hash": "204674eb811a35ba00447c090c783777da3a020da4f14a3ee0f335e1cfb15481"
621
+ },
622
+ {
623
+ "instrument_id": "PAY0125",
624
+ "type": "upi",
625
+ "last4_hash": "63892e272bb1f0641fd09ad8fe6a1e8192c901dabe381ae4406f9864d6ae33af"
626
+ },
627
+ {
628
+ "instrument_id": "PAY0126",
629
+ "type": "upi",
630
+ "last4_hash": "5aed8261495824595ecc62f78b62a0a2d4db3cdee22034652d7da89ca7c3e5f5"
631
+ },
632
+ {
633
+ "instrument_id": "PAY0127",
634
+ "type": "card",
635
+ "last4_hash": "bc805497f86694cef2e1cf6df62989cb818f69a80f4fb170e5b7cd66aef5d64b"
636
+ },
637
+ {
638
+ "instrument_id": "PAY0128",
639
+ "type": "card",
640
+ "last4_hash": "3b040bdd21634e7a8d08619c89e37e4c8430e955fc88e3888b1c13bda6ec32f3"
641
+ },
642
+ {
643
+ "instrument_id": "PAY0129",
644
+ "type": "card",
645
+ "last4_hash": "3141d9c749e88ee5550192703c3e025a9b3446ec543f17a923cb24e7b61dfece"
646
+ },
647
+ {
648
+ "instrument_id": "PAY0130",
649
+ "type": "upi",
650
+ "last4_hash": "cf14794b7a9b6f825719bdda892b9045b829a28de7b72f97b5cff93acd8d1433"
651
+ },
652
+ {
653
+ "instrument_id": "PAY0131",
654
+ "type": "upi",
655
+ "last4_hash": "ce048a27aa4bec0b76ec7ae0641f3922a3aa58625051690fedf4994419c320db"
656
+ },
657
+ {
658
+ "instrument_id": "PAY0132",
659
+ "type": "upi",
660
+ "last4_hash": "0c9bd7be38dee8ce8a5b02f67b9d656793177d9849808032ad412708b018159c"
661
+ },
662
+ {
663
+ "instrument_id": "PAY0133",
664
+ "type": "card",
665
+ "last4_hash": "e0b372a86dbfe71bdb7bf19790cacabd2ad2c4d145087a94d842fe6ffe2eef8b"
666
+ },
667
+ {
668
+ "instrument_id": "PAY0134",
669
+ "type": "card",
670
+ "last4_hash": "154d72ced7915c97e73258a22c08e202e8974629b1847c87d516fa6e692672a4"
671
+ },
672
+ {
673
+ "instrument_id": "PAY0135",
674
+ "type": "upi",
675
+ "last4_hash": "a8abbe1f7b05c096d4875eb9a7bdc9d8b539bd230ee8a1e02d2346bb91c2c080"
676
+ },
677
+ {
678
+ "instrument_id": "PAY0136",
679
+ "type": "card",
680
+ "last4_hash": "aeba47c17ca09557808c333b49af210a0209aff6b4334fa7192aaed69c5f36dd"
681
+ },
682
+ {
683
+ "instrument_id": "PAY0137",
684
+ "type": "upi",
685
+ "last4_hash": "708cdcc0590a516387b0a516be7719fc8ad25d5653c6d22cf3ae6d690cf9d126"
686
+ },
687
+ {
688
+ "instrument_id": "PAY0138",
689
+ "type": "upi",
690
+ "last4_hash": "2013ea8b947491e0829514c81c52326ae69a14c1cc664630584252db2c63de82"
691
+ },
692
+ {
693
+ "instrument_id": "PAY0139",
694
+ "type": "upi",
695
+ "last4_hash": "817856dcff013f1e9f1a8edb78c4e86baaba6fcf7d80b6d941a1c4cf888cc32b"
696
+ },
697
+ {
698
+ "instrument_id": "PAY0140",
699
+ "type": "card",
700
+ "last4_hash": "0287240ca63d3c73cb1231b69fce2315e999f3fce091140acf6ec1c6e5ba094f"
701
+ },
702
+ {
703
+ "instrument_id": "PAY0141",
704
+ "type": "upi",
705
+ "last4_hash": "6de4e9cef3c5ae3c7fffbac619b70bfd7a22202d2e509456cbcad3b2fed89b67"
706
+ },
707
+ {
708
+ "instrument_id": "PAY0142",
709
+ "type": "card",
710
+ "last4_hash": "fc2cd6cb85339b6c15848a08e7713f73cf55148b21cb32fb78cdf5ff44c90634"
711
+ },
712
+ {
713
+ "instrument_id": "PAY0143",
714
+ "type": "upi",
715
+ "last4_hash": "771e6ad04e8013ba5c223e96eb0bc38e3204a962bceac2aa4b84103c40472d8a"
716
+ },
717
+ {
718
+ "instrument_id": "PAY0144",
719
+ "type": "card",
720
+ "last4_hash": "e209f7b7bfe0f9ccd9c6b5d8aa497b55b76812f56ff0cddd6fc9f703f17192b3"
721
+ },
722
+ {
723
+ "instrument_id": "PAY0145",
724
+ "type": "upi",
725
+ "last4_hash": "21ed781581839a96745dd321ed9348facf34331e26259f530953741a22503ece"
726
+ },
727
+ {
728
+ "instrument_id": "PAY0146",
729
+ "type": "upi",
730
+ "last4_hash": "d4f4ef122bb30e6818329d5fda158711c5b9384bc86a8c7bcfe7cd85435c8a6d"
731
+ },
732
+ {
733
+ "instrument_id": "PAY0147",
734
+ "type": "card",
735
+ "last4_hash": "79c56d57697ddb331d1e1a1b62fa940d1579447b35221a7f6b68924dfce14244"
736
+ },
737
+ {
738
+ "instrument_id": "PAY0148",
739
+ "type": "card",
740
+ "last4_hash": "0c921bbabc0503bc23abfa92b5a4dab3c14f362b16096238fb4a71c279bc0eeb"
741
+ },
742
+ {
743
+ "instrument_id": "PAY0149",
744
+ "type": "upi",
745
+ "last4_hash": "0405a13200aa02e63862cbfd66a5beb1129f206eab06fe05ec0b8dc0217825b0"
746
+ },
747
+ {
748
+ "instrument_id": "PAY0150",
749
+ "type": "upi",
750
+ "last4_hash": "2be4f967ce834c65b10d2f91f45ab8fbfe083b468a6247363056ff29091d34bc"
751
+ },
752
+ {
753
+ "instrument_id": "PAY0151",
754
+ "type": "upi",
755
+ "last4_hash": "a5382c5d33b1be5721daf7b45e98810db32b230b67f5ffe23287be1480a6068c"
756
+ },
757
+ {
758
+ "instrument_id": "PAY0152",
759
+ "type": "upi",
760
+ "last4_hash": "09a9221450d87d97c48b18fde1c53ca2214c426f90d2207a3d66c7e65b6d3da8"
761
+ },
762
+ {
763
+ "instrument_id": "PAY0153",
764
+ "type": "upi",
765
+ "last4_hash": "03438f89353aee02887a151bd0d762f4bd10f7ec9d9d0e4536df399c92ae4103"
766
+ },
767
+ {
768
+ "instrument_id": "PAY0154",
769
+ "type": "upi",
770
+ "last4_hash": "f802afc52a957e1ed75ee48fbd23ee909a57f3253aaa28ee9080b1a6ae11e48e"
771
+ },
772
+ {
773
+ "instrument_id": "PAY0155",
774
+ "type": "card",
775
+ "last4_hash": "5078264a209e1ee0e3a8a46a11d840dfd1879500f7fd02aef1f8b6a2276a5be9"
776
+ },
777
+ {
778
+ "instrument_id": "PAY0156",
779
+ "type": "upi",
780
+ "last4_hash": "b7b90855c2dce7710c8e11ac3de1bbaea0656042dae41ec753f843a8ff372c9f"
781
+ },
782
+ {
783
+ "instrument_id": "PAY0157",
784
+ "type": "upi",
785
+ "last4_hash": "313b93b4b6589d56dcc1baaaccca223a337628b43d2ef51af234b03c242b9895"
786
+ },
787
+ {
788
+ "instrument_id": "PAY0158",
789
+ "type": "card",
790
+ "last4_hash": "02be6761f02f2291c1fe54c103440f7b9ba65da74dc84f1e08736d7a67824643"
791
+ },
792
+ {
793
+ "instrument_id": "PAY0159",
794
+ "type": "upi",
795
+ "last4_hash": "11e62f15fe7bc8ee83afaf873048a419a8aea5ef444c49e73bc86bd3968b9d01"
796
+ },
797
+ {
798
+ "instrument_id": "PAY0160",
799
+ "type": "upi",
800
+ "last4_hash": "541823e83415b95589e462d2508c4f67104b90322e542373205b7c62d1ed4468"
801
+ },
802
+ {
803
+ "instrument_id": "PAY0161",
804
+ "type": "card",
805
+ "last4_hash": "b4eed30197a01d2d80fb165bd90e21ebe54ec513216a8f03a3ed263d98f00f03"
806
+ },
807
+ {
808
+ "instrument_id": "PAY0162",
809
+ "type": "upi",
810
+ "last4_hash": "ead77f513269818b47d13226728ff3c031ead406abb8d0d70c247da00aa9eb75"
811
+ },
812
+ {
813
+ "instrument_id": "PAY0163",
814
+ "type": "card",
815
+ "last4_hash": "deb4666a75564b9fceea3b10aab44ec24089d914d7bb262b51145392856e3714"
816
+ },
817
+ {
818
+ "instrument_id": "PAY0164",
819
+ "type": "card",
820
+ "last4_hash": "b5a711fe7e92051816302057edc97e9e4a471802b96e379e8d382b4e9f248478"
821
+ },
822
+ {
823
+ "instrument_id": "PAY0165",
824
+ "type": "upi",
825
+ "last4_hash": "0248b9e54ef1e41897a3198b509166905ab9d941758886bbfb03bd72449bf63e"
826
+ },
827
+ {
828
+ "instrument_id": "PAY0166",
829
+ "type": "upi",
830
+ "last4_hash": "22d3ae0abdf720e6adb3f0e4b81366e3fbeee65658825bf2f10f43fa92c1f298"
831
+ },
832
+ {
833
+ "instrument_id": "PAY0167",
834
+ "type": "upi",
835
+ "last4_hash": "7ceb89878579e1cb86a8fd6fb4a5cc8abad8660793354ee9b9d0371c3fb4f924"
836
+ },
837
+ {
838
+ "instrument_id": "PAY0168",
839
+ "type": "upi",
840
+ "last4_hash": "de7217c8927ffa8f8f97c5871b9c2c1d5f543b115cc71aa30150439c42e5fb71"
841
+ },
842
+ {
843
+ "instrument_id": "PAY0169",
844
+ "type": "card",
845
+ "last4_hash": "28af0d12785efe04f62544cbd49382ebbce76256e800fecaeb4e4b7871d402bb"
846
+ },
847
+ {
848
+ "instrument_id": "PAY0170",
849
+ "type": "card",
850
+ "last4_hash": "4708f9c61a5e58b72774193f69bf37442423a4d2673e1f2cdc3f07f2614aba5d"
851
+ },
852
+ {
853
+ "instrument_id": "PAY0171",
854
+ "type": "card",
855
+ "last4_hash": "fa2fd37dfe0f812d3dc55fb445a1a4d04b95032b6753f0d4943313655bd3f8e8"
856
+ },
857
+ {
858
+ "instrument_id": "PAY0172",
859
+ "type": "upi",
860
+ "last4_hash": "01f955f2f2122036a9cc5ed2a780461307978fddff83f39da70b6d63a4d2b2e8"
861
+ },
862
+ {
863
+ "instrument_id": "PAY0173",
864
+ "type": "card",
865
+ "last4_hash": "747edaa66dec70dc1256e51b15be0fe658902a7af7a427db024df3431678152c"
866
+ },
867
+ {
868
+ "instrument_id": "PAY0174",
869
+ "type": "upi",
870
+ "last4_hash": "a81e1a8dde5c3b93cdb41a6b6cc6648a5131af5cd3ebe6b79ebb6fe97c3b418e"
871
+ },
872
+ {
873
+ "instrument_id": "PAY0175",
874
+ "type": "upi",
875
+ "last4_hash": "b9447cbd1457eb1c2caded2e63e3a8fd05d507415f6ad4afcf49ccc128dc5697"
876
+ },
877
+ {
878
+ "instrument_id": "PAY0176",
879
+ "type": "card",
880
+ "last4_hash": "228971770cc3111e6ce32b17a5ab6ae7c0ba993634ac8d83fd32ec6e91615995"
881
+ },
882
+ {
883
+ "instrument_id": "PAY0177",
884
+ "type": "upi",
885
+ "last4_hash": "6f7cef78841fc154d6bfb8be9bf2a47e83608cdac4a926d63dc80ffd83a66378"
886
+ },
887
+ {
888
+ "instrument_id": "PAY0178",
889
+ "type": "card",
890
+ "last4_hash": "80e107ee3ba12129c178396a4b3a4c502210b378e8b85d3b14087d7becbcfde7"
891
+ },
892
+ {
893
+ "instrument_id": "PAY0179",
894
+ "type": "card",
895
+ "last4_hash": "0c064516a0ec5302a2d2c2c270b509ca7a100b012445086f8900891310bdb9f3"
896
+ },
897
+ {
898
+ "instrument_id": "PAY0180",
899
+ "type": "upi",
900
+ "last4_hash": "f4fcc2f7dccbda17aa92ab8605e0fdd2f9861670fb8c5738241a029c8fc617ae"
901
+ },
902
+ {
903
+ "instrument_id": "PAY0181",
904
+ "type": "upi",
905
+ "last4_hash": "cb898ba3b47402b2495eb12f3902cb3e82775f59b3a8d9f726c5c9ebb8016352"
906
+ },
907
+ {
908
+ "instrument_id": "PAY0182",
909
+ "type": "upi",
910
+ "last4_hash": "7c27c01a452ad2badbecfd322f506a6548830e9a8e35c981c14b670cd7f70c95"
911
+ },
912
+ {
913
+ "instrument_id": "PAY0183",
914
+ "type": "upi",
915
+ "last4_hash": "9cc2c4fccfd99215e979acb8954a605c5c78b62013aa22c501bb8b8a214114c6"
916
+ },
917
+ {
918
+ "instrument_id": "PAY0184",
919
+ "type": "upi",
920
+ "last4_hash": "8dcedc831b40346fadc5ca491a72ba1dad3f3b68ac3296ef7ef13bdb9082b92b"
921
+ },
922
+ {
923
+ "instrument_id": "PAY0185",
924
+ "type": "upi",
925
+ "last4_hash": "a724557fb7d3f2c6838beccb4ad12f8be4e5d8383529b8c7f53ab4544c085ea9"
926
+ },
927
+ {
928
+ "instrument_id": "PAY0186",
929
+ "type": "upi",
930
+ "last4_hash": "49d223c477447c4bdb7061d8b342eb725eed77cf8e8a0c2e99e19328fd7ca39d"
931
+ },
932
+ {
933
+ "instrument_id": "PAY0187",
934
+ "type": "upi",
935
+ "last4_hash": "bf3433d6d20b35fe62cb7b46f00c64ab984686f7a708ce00277ee3c26fae51f2"
936
+ },
937
+ {
938
+ "instrument_id": "PAY0188",
939
+ "type": "upi",
940
+ "last4_hash": "a992b986fc27b23a80c1c673ecfbbbc8957647865f50c63d1c30be78311f0cce"
941
+ },
942
+ {
943
+ "instrument_id": "PAY0189",
944
+ "type": "upi",
945
+ "last4_hash": "04002abb101a19fcabea0f4b87b8b79fe73dee47233d463fcbd9e65d17a6ef8d"
946
+ },
947
+ {
948
+ "instrument_id": "PAY0190",
949
+ "type": "card",
950
+ "last4_hash": "a24a6dd3d30dc6b15f3e9ea581805d199270323842004e03ddf3c932478b1f11"
951
+ },
952
+ {
953
+ "instrument_id": "PAY0191",
954
+ "type": "card",
955
+ "last4_hash": "35c7a070e74b4380b5cb8cb0b68f3a06b331e4477b720c43df7af5c4c36fd2c0"
956
+ },
957
+ {
958
+ "instrument_id": "PAY0192",
959
+ "type": "card",
960
+ "last4_hash": "5c6ab6a10221871a18b25558a77a99d1324732e4d5ac403e0bed5d85acba24fd"
961
+ },
962
+ {
963
+ "instrument_id": "PAY0193",
964
+ "type": "card",
965
+ "last4_hash": "1f61c05e5a94a1995a4dbaf9e3b9280190246279d7d9a0f229b16627fe15845a"
966
+ },
967
+ {
968
+ "instrument_id": "PAY0194",
969
+ "type": "card",
970
+ "last4_hash": "1628e107773c273d5509e2051386207d1b75f1dc3a97e6e4844e132837fae396"
971
+ },
972
+ {
973
+ "instrument_id": "PAY0195",
974
+ "type": "upi",
975
+ "last4_hash": "246b0ab60280d23005e7aaf3941fced7b84e933dd5e05be3b404e90dea26368d"
976
+ },
977
+ {
978
+ "instrument_id": "PAY0196",
979
+ "type": "upi",
980
+ "last4_hash": "8aa885ab588858db215e6b851dab0bc5ea5c2f058a0cc193501e56d30e5c6143"
981
+ },
982
+ {
983
+ "instrument_id": "PAY0197",
984
+ "type": "card",
985
+ "last4_hash": "3b96b9d2dc7cd7d338d73571d7c46eefe383955e4427d8a2079f4c41b374d988"
986
+ },
987
+ {
988
+ "instrument_id": "PAY0198",
989
+ "type": "upi",
990
+ "last4_hash": "847cf2f668d88b1db87b3e8a2e991510040236da249d12e2beaa36c15e34dc09"
991
+ },
992
+ {
993
+ "instrument_id": "PAY0199",
994
+ "type": "card",
995
+ "last4_hash": "153de641ea0557acad28b609f949ec7ad909c0168b9af1d810fcf26e45e7e428"
996
+ },
997
+ {
998
+ "instrument_id": "PAY0200",
999
+ "type": "card",
1000
+ "last4_hash": "1adb67461a4c3739283e6b97ba7984e0d4b024a4dcbf1f341c389f0c581fde4a"
1001
+ },
1002
+ {
1003
+ "instrument_id": "PAY0201",
1004
+ "type": "card",
1005
+ "last4_hash": "5f9e3d4a0fa6f44a726c146ead2a301eb4026ae67b06cc5971210e5d749762b0"
1006
+ },
1007
+ {
1008
+ "instrument_id": "PAY0202",
1009
+ "type": "card",
1010
+ "last4_hash": "647011fb41f77ae398ca6e97e8ee589270760023219a9a1f6d6eaf0055c75c8d"
1011
+ },
1012
+ {
1013
+ "instrument_id": "PAY0203",
1014
+ "type": "card",
1015
+ "last4_hash": "baa8a6ecbabbd5ef02222983b23a4fbd1d794cca68c767e07d67af2d9f46e737"
1016
+ },
1017
+ {
1018
+ "instrument_id": "PAY0204",
1019
+ "type": "upi",
1020
+ "last4_hash": "c6e1a3b42f1021a8897367a8ecc5906409856ad96b3429d9209f56abb44a1773"
1021
+ },
1022
+ {
1023
+ "instrument_id": "PAY0205",
1024
+ "type": "card",
1025
+ "last4_hash": "ee4f8781fe28ccc751937330fc03ce427460b7ad4a0cf741019863eba4e53a6c"
1026
+ },
1027
+ {
1028
+ "instrument_id": "PAY0206",
1029
+ "type": "card",
1030
+ "last4_hash": "15197cf7214b58e67cae565e573ffd9aa44bcb81f8ee5d7185dfe8da0a16ef43"
1031
+ },
1032
+ {
1033
+ "instrument_id": "PAY0207",
1034
+ "type": "upi",
1035
+ "last4_hash": "46f292108babd9c0500ef511728ac5099eb586c255eebc72ab2499c6df520384"
1036
+ },
1037
+ {
1038
+ "instrument_id": "PAY0208",
1039
+ "type": "upi",
1040
+ "last4_hash": "331160121aa00fa6b8954bf8b72f9dcfead4093d79b1ed0e2b50379b9e53479c"
1041
+ },
1042
+ {
1043
+ "instrument_id": "PAY0209",
1044
+ "type": "upi",
1045
+ "last4_hash": "8f3cd17919924b85057eace5ba32fda81fff13141c996693288487601ea4a717"
1046
+ },
1047
+ {
1048
+ "instrument_id": "PAY0210",
1049
+ "type": "upi",
1050
+ "last4_hash": "d7defea67028f03ef163788c9c2d89392f5b26588771452c170625109f2f3406"
1051
+ },
1052
+ {
1053
+ "instrument_id": "PAY0211",
1054
+ "type": "upi",
1055
+ "last4_hash": "6588bc3e00cce5a692f811a8c1e96c9c8d22168939143dc6713258fdd4da54e2"
1056
+ },
1057
+ {
1058
+ "instrument_id": "PAY0212",
1059
+ "type": "card",
1060
+ "last4_hash": "9e645831962e43f534afd60532328ba5ab4eefea6e73765fb9690e4d395112f8"
1061
+ },
1062
+ {
1063
+ "instrument_id": "PAY0213",
1064
+ "type": "upi",
1065
+ "last4_hash": "fcbaac549e7b8ade5d0f9826413f5bd2c0beb47df11d76c889c9b417d2446295"
1066
+ },
1067
+ {
1068
+ "instrument_id": "PAY0214",
1069
+ "type": "upi",
1070
+ "last4_hash": "9fd6e8b29087ba21fabbc6c46979750b8008ed05ba77b9e3ff032b3bd3d5992e"
1071
+ },
1072
+ {
1073
+ "instrument_id": "PAY0215",
1074
+ "type": "card",
1075
+ "last4_hash": "06c973e49be9fdbfdaa84a397b35b05baceec93d5ea35e4cba2d6eaecf97700f"
1076
+ },
1077
+ {
1078
+ "instrument_id": "PAY0216",
1079
+ "type": "upi",
1080
+ "last4_hash": "c4af926b851b86d5aeb3264a3f62db4b4944d664be2b9570512764afd8223b18"
1081
+ },
1082
+ {
1083
+ "instrument_id": "PAY0217",
1084
+ "type": "upi",
1085
+ "last4_hash": "a276a4e96e2b4b8f8ce5ed09536cb5cd0df9b7c12351e00aa839c0d209b8a175"
1086
+ },
1087
+ {
1088
+ "instrument_id": "PAY0218",
1089
+ "type": "card",
1090
+ "last4_hash": "8a80efe71cf1850186e908f82e61d9ab6c95889d927cfec59d69f8aeb0673ff7"
1091
+ },
1092
+ {
1093
+ "instrument_id": "PAY0219",
1094
+ "type": "card",
1095
+ "last4_hash": "2a0400a50c04a5c64e7818f4920059acbedcc2a193e715b0f0310923405b36e8"
1096
+ },
1097
+ {
1098
+ "instrument_id": "PAY0220",
1099
+ "type": "upi",
1100
+ "last4_hash": "29a217fcc45f734ac6d51f8c61d010cb40b8fb2b73e3d626b6ff3772c113a7a0"
1101
+ },
1102
+ {
1103
+ "instrument_id": "PAY0221",
1104
+ "type": "card",
1105
+ "last4_hash": "b6cfb9e281f64066b6cb04d5d9108276f4e3ecbc68c860831c481ba932591673"
1106
+ },
1107
+ {
1108
+ "instrument_id": "PAY0222",
1109
+ "type": "card",
1110
+ "last4_hash": "2a2ea325451e51e5f810010975fad2f66ad56342d85586d74bc0a2b3e31c8356"
1111
+ },
1112
+ {
1113
+ "instrument_id": "PAY0223",
1114
+ "type": "card",
1115
+ "last4_hash": "8d3498f1ebcbd5f1bd973e184059a6f0e06812c898d4375da115bdedff492d0f"
1116
+ },
1117
+ {
1118
+ "instrument_id": "PAY0224",
1119
+ "type": "upi",
1120
+ "last4_hash": "f36a35c022224e38de6430182e0a0fa554ce384997ce1086599f6317e832d9be"
1121
+ },
1122
+ {
1123
+ "instrument_id": "PAY0225",
1124
+ "type": "upi",
1125
+ "last4_hash": "1a18b0a3dde64484d80eddbf00483e3681e94109324ae8368917e5ec79d0b634"
1126
+ },
1127
+ {
1128
+ "instrument_id": "PAY0226",
1129
+ "type": "upi",
1130
+ "last4_hash": "1991e7b38912c102c0207ee57f8fa9a5ef96f1e93ae277d7e6bb0e35d8a23483"
1131
+ },
1132
+ {
1133
+ "instrument_id": "PAY0227",
1134
+ "type": "upi",
1135
+ "last4_hash": "841bed395f6f9ce6dc0d396f472fc2e31dab0dcd0def14798686d8518e6c34fb"
1136
+ },
1137
+ {
1138
+ "instrument_id": "PAY0228",
1139
+ "type": "upi",
1140
+ "last4_hash": "96f227129556c6f3080ebfe4619c520c6ee640fec486c406dc28cd95da33b3fd"
1141
+ },
1142
+ {
1143
+ "instrument_id": "PAY0229",
1144
+ "type": "upi",
1145
+ "last4_hash": "cc4a428f3be823f76b73d2e772096feb84244db682b479d1038d58d0fa278b30"
1146
+ },
1147
+ {
1148
+ "instrument_id": "PAY0230",
1149
+ "type": "card",
1150
+ "last4_hash": "63727ee4cff30e597899af01a4a355ea91e54c9a055e1da18e25b89dfa90352c"
1151
+ },
1152
+ {
1153
+ "instrument_id": "PAY0231",
1154
+ "type": "card",
1155
+ "last4_hash": "3d19e945ed5a25a399da3036bc862275296544218de8260d47a5ad3d9b853af4"
1156
+ },
1157
+ {
1158
+ "instrument_id": "PAY0232",
1159
+ "type": "upi",
1160
+ "last4_hash": "5ad4c5cccee58bdc87eda85a48e67559432b3ebb4092d49cb142d8ed206bfc7c"
1161
+ },
1162
+ {
1163
+ "instrument_id": "PAY0233",
1164
+ "type": "card",
1165
+ "last4_hash": "a948b46c0f1890667de7b60fb490c573d60c647158f91193dd915afde693529c"
1166
+ },
1167
+ {
1168
+ "instrument_id": "PAY0234",
1169
+ "type": "card",
1170
+ "last4_hash": "f23784f17468ea72b2dd3fd4e4faecb40ccdd24f39584fcd283c391d4faf87c8"
1171
+ },
1172
+ {
1173
+ "instrument_id": "PAY0235",
1174
+ "type": "card",
1175
+ "last4_hash": "739dbef65f3b1e4117d7bdb2a76efa5b91943f8f26ab74a7c1cad9feb0324129"
1176
+ },
1177
+ {
1178
+ "instrument_id": "PAY0236",
1179
+ "type": "card",
1180
+ "last4_hash": "93352a86094a964ef548e72c79a9f6187b7f561773c31f3371e86ecffe9a4dd9"
1181
+ },
1182
+ {
1183
+ "instrument_id": "PAY0237",
1184
+ "type": "upi",
1185
+ "last4_hash": "9569757a6fce9018ec528b20fc71028be89e24df343779ff25a281d38a330a3d"
1186
+ },
1187
+ {
1188
+ "instrument_id": "PAY0238",
1189
+ "type": "upi",
1190
+ "last4_hash": "28ef70984a8eb80ae8606716d8824302fa6e616bb525846f7088e774ce66684c"
1191
+ },
1192
+ {
1193
+ "instrument_id": "PAY0239",
1194
+ "type": "upi",
1195
+ "last4_hash": "b44cb9e82dd50599a74e865bd98505ff40dd1f873ff236bbac53b7982a678678"
1196
+ },
1197
+ {
1198
+ "instrument_id": "PAY0240",
1199
+ "type": "card",
1200
+ "last4_hash": "7b4c3b903da50f31cacf58554525088f6ca687144330569605c8de3a404d2ce6"
1201
+ },
1202
+ {
1203
+ "instrument_id": "PAY0241",
1204
+ "type": "upi",
1205
+ "last4_hash": "0bf788aaa8956e3115a533477b96a937c64a7cd80f318540fb2ce36ebd4fcf32"
1206
+ },
1207
+ {
1208
+ "instrument_id": "PAY0242",
1209
+ "type": "upi",
1210
+ "last4_hash": "4e29a008959a9ddb23bd5d117d6a71f1e75f3b17df6ca4dd313a16711ea6ec45"
1211
+ },
1212
+ {
1213
+ "instrument_id": "PAY0243",
1214
+ "type": "upi",
1215
+ "last4_hash": "3206fba387884735f6ec58259d62fe0943b0ee14fe9dc5137cc676428cb07bb9"
1216
+ },
1217
+ {
1218
+ "instrument_id": "PAY0244",
1219
+ "type": "card",
1220
+ "last4_hash": "8f10833e3ef270a248b7001d1f7b30b7d05e67d648ce3e87f000215e38451b7b"
1221
+ },
1222
+ {
1223
+ "instrument_id": "PAY0245",
1224
+ "type": "upi",
1225
+ "last4_hash": "a10b8d76c071f9e86d3906e6e15cf75709a4c6b2d7439ec2cc44e97f08119936"
1226
+ },
1227
+ {
1228
+ "instrument_id": "PAY0246",
1229
+ "type": "card",
1230
+ "last4_hash": "9da8780a381915da91ad2693249937745283266704661097a162c48c38976205"
1231
+ },
1232
+ {
1233
+ "instrument_id": "PAY0247",
1234
+ "type": "card",
1235
+ "last4_hash": "c44066cadb3f91efc94e8a80b0ed370d9e9775d66fad1dac4e546bf9f1916df9"
1236
+ },
1237
+ {
1238
+ "instrument_id": "PAY0248",
1239
+ "type": "card",
1240
+ "last4_hash": "6b724a6630ccac9ac84a7af1031414c6b6b6a16b59aafe635d5b93b12d44c7bf"
1241
+ },
1242
+ {
1243
+ "instrument_id": "PAY0249",
1244
+ "type": "upi",
1245
+ "last4_hash": "6ac7bd0865c7d5d212b9a9b1c92bb4b85a3a06e03e9bbc458782ad4c9382345f"
1246
+ },
1247
+ {
1248
+ "instrument_id": "PAY0250",
1249
+ "type": "card",
1250
+ "last4_hash": "e2135c76ca7863e51154829de3d9cb0a8f6398d31518c7581d370ed9cc521811"
1251
+ },
1252
+ {
1253
+ "instrument_id": "PAY0251",
1254
+ "type": "card",
1255
+ "last4_hash": "45d1bf544943b021e561523370fcb4b73bf8c67de1b68a3ec825fb28144ff434"
1256
+ },
1257
+ {
1258
+ "instrument_id": "PAY0252",
1259
+ "type": "upi",
1260
+ "last4_hash": "22ea6cd8dc9b0b8247357f4c1736437b7cf6d5e886f8a386948476e7f72b0fc4"
1261
+ },
1262
+ {
1263
+ "instrument_id": "PAY0253",
1264
+ "type": "upi",
1265
+ "last4_hash": "5d6076ec8f36560a68a9a3d910e459f6250c5acb00237a9558102f863a4955ab"
1266
+ },
1267
+ {
1268
+ "instrument_id": "PAY0254",
1269
+ "type": "upi",
1270
+ "last4_hash": "89f7bcb6f95d9f29063738e097b0255e0bcc57a39caeff49ddf6925789120fc5"
1271
+ },
1272
+ {
1273
+ "instrument_id": "PAY0255",
1274
+ "type": "card",
1275
+ "last4_hash": "1b457162acd3bc249096a759943769e27b27ecbd1fb20a870b70b82e9c0264d6"
1276
+ },
1277
+ {
1278
+ "instrument_id": "PAY0256",
1279
+ "type": "upi",
1280
+ "last4_hash": "4e52a13115d05bc553e5de70cba00a6ff3ec75172ab1015133999f4907b06739"
1281
+ },
1282
+ {
1283
+ "instrument_id": "PAY0257",
1284
+ "type": "upi",
1285
+ "last4_hash": "ce9020dc8536f77c86e0179914a12928e8171560a9f865f50a905f4b175f2fe2"
1286
+ },
1287
+ {
1288
+ "instrument_id": "PAY0258",
1289
+ "type": "upi",
1290
+ "last4_hash": "c11930573da1982285db8a450fbe77496b12ff78b3585ba26f70d4c9fa325dac"
1291
+ },
1292
+ {
1293
+ "instrument_id": "PAY0259",
1294
+ "type": "upi",
1295
+ "last4_hash": "e346f2da1da79fb8f2e685ba08e28022f4f11c15169f74e7fc17d8bfce87865f"
1296
+ },
1297
+ {
1298
+ "instrument_id": "PAY0260",
1299
+ "type": "upi",
1300
+ "last4_hash": "f766d02d1d0991fdde7d1ac9e3e1a5164f68f3bd79328a1a42c5c9484237606b"
1301
+ },
1302
+ {
1303
+ "instrument_id": "PAY0261",
1304
+ "type": "card",
1305
+ "last4_hash": "ef0d2e96b08cee59a3557ca2ddba84bd84b4dabd4e2f882122a291bffee0acdd"
1306
+ },
1307
+ {
1308
+ "instrument_id": "PAY0262",
1309
+ "type": "upi",
1310
+ "last4_hash": "3760c7faec9d9f32031e20781e1123f31b2085d3407995ea8f9c195a90fbe0b0"
1311
+ },
1312
+ {
1313
+ "instrument_id": "PAY0263",
1314
+ "type": "upi",
1315
+ "last4_hash": "7c5fb304ddb31dad9639fe0566854024009252dd1edc43cee8d3b2fefe63c2cc"
1316
+ },
1317
+ {
1318
+ "instrument_id": "PAY0264",
1319
+ "type": "upi",
1320
+ "last4_hash": "694c4311e4d3d0aa2d05a079ec09c9bffbbbf38ccee37912e59fcfa13d0cf36b"
1321
+ },
1322
+ {
1323
+ "instrument_id": "PAY0265",
1324
+ "type": "card",
1325
+ "last4_hash": "94f136bbfce57e5c6f1c86bbf6bf8a4e1d48ef230373eda2859170e158b1c185"
1326
+ },
1327
+ {
1328
+ "instrument_id": "PAY0266",
1329
+ "type": "card",
1330
+ "last4_hash": "462b01670e50875a9df97c18e87cd367dbe7fbd6f32783be20883747e37d5af6"
1331
+ },
1332
+ {
1333
+ "instrument_id": "PAY0267",
1334
+ "type": "upi",
1335
+ "last4_hash": "b5c6ea4587d7483b25440b9af696a3cf45757a33dc540449fff4d084d5669179"
1336
+ },
1337
+ {
1338
+ "instrument_id": "PAY0268",
1339
+ "type": "card",
1340
+ "last4_hash": "0228374d12ee995cdeff8e25d819990f80d40c8773321fafe4ce1572c7df29af"
1341
+ },
1342
+ {
1343
+ "instrument_id": "PAY0269",
1344
+ "type": "upi",
1345
+ "last4_hash": "dbcbd844304e9fc9edd4bead28de1ce924bb41b2a4a602591ac1a4051c5f426f"
1346
+ },
1347
+ {
1348
+ "instrument_id": "PAY0270",
1349
+ "type": "upi",
1350
+ "last4_hash": "cccbf5f0cdc758831aec3f19afd46cfa357cc9b667b1eeb3c1a93776e824df10"
1351
+ },
1352
+ {
1353
+ "instrument_id": "PAY0271",
1354
+ "type": "upi",
1355
+ "last4_hash": "d34e5be7cf681f8f8f5e2aca5d0b120463f3d560a2b827ce0d12a8534600d179"
1356
+ },
1357
+ {
1358
+ "instrument_id": "PAY0272",
1359
+ "type": "card",
1360
+ "last4_hash": "e1d07bb0923258ae0e244dcf81f4c87614b833fc15ca7dbb77740d1860f915d2"
1361
+ },
1362
+ {
1363
+ "instrument_id": "PAY0273",
1364
+ "type": "upi",
1365
+ "last4_hash": "8da78c744ccbc95e9980bb28010978baaae42fdc0341cdc6c52e49df0bec5337"
1366
+ },
1367
+ {
1368
+ "instrument_id": "PAY0274",
1369
+ "type": "card",
1370
+ "last4_hash": "b1e9300609f0a4556b09d64f62814c5e0e839e4353f262869bbd78e745f458b1"
1371
+ },
1372
+ {
1373
+ "instrument_id": "PAY0275",
1374
+ "type": "upi",
1375
+ "last4_hash": "9dd391923f6fcb022c671681f977b1c8220bd55d67bbf4cf92c7134765ac1110"
1376
+ },
1377
+ {
1378
+ "instrument_id": "PAY0276",
1379
+ "type": "upi",
1380
+ "last4_hash": "666a8b82b2fa22b380990b945a7253edb81077025e894f9a240f79e34e12073f"
1381
+ },
1382
+ {
1383
+ "instrument_id": "PAY0277",
1384
+ "type": "upi",
1385
+ "last4_hash": "a44c062db40c7f0dfa7759a203df799c3ec1481692f1f83b0f0ac0982949cebf"
1386
+ },
1387
+ {
1388
+ "instrument_id": "PAY0278",
1389
+ "type": "upi",
1390
+ "last4_hash": "6ea839f917ae2c0a0f39dee4625f232a1d6437678c541ecd3c94f1954e4c3159"
1391
+ },
1392
+ {
1393
+ "instrument_id": "PAY0279",
1394
+ "type": "card",
1395
+ "last4_hash": "dd6103b473273ecdbf0446eb7e22d5e3397e03f2e275379e4427a36cfc6e87e6"
1396
+ },
1397
+ {
1398
+ "instrument_id": "PAY0280",
1399
+ "type": "upi",
1400
+ "last4_hash": "cec4f851cb0a168f0193b99eddefa1a34121054b40755447ea1a3366d843242a"
1401
+ },
1402
+ {
1403
+ "instrument_id": "PAY0281",
1404
+ "type": "upi",
1405
+ "last4_hash": "69fc261f5af1673a971aa89d9ed10b17c59df9ef4297d175768431999fbd880e"
1406
+ },
1407
+ {
1408
+ "instrument_id": "PAY0282",
1409
+ "type": "upi",
1410
+ "last4_hash": "cad2316eaa81e19e7bf2c3f0033243acf6cef44d18c5fa5e8ad54d490d68bde7"
1411
+ },
1412
+ {
1413
+ "instrument_id": "PAY0283",
1414
+ "type": "upi",
1415
+ "last4_hash": "c2c26a9990dd4dfa95ad0087849ddb39141a04454ad2377c1a2c35b3a388b57b"
1416
+ },
1417
+ {
1418
+ "instrument_id": "PAY0284",
1419
+ "type": "upi",
1420
+ "last4_hash": "beb62c82d72fd3a83113c930c0c3139e9418636d9e03ab36cec1956eed69524c"
1421
+ },
1422
+ {
1423
+ "instrument_id": "PAY0285",
1424
+ "type": "card",
1425
+ "last4_hash": "94bcfe105ad50803f9a5dc7f60974e21d47f608826675250c699467f116608dc"
1426
+ },
1427
+ {
1428
+ "instrument_id": "PAY0286",
1429
+ "type": "card",
1430
+ "last4_hash": "16d5164c6ff6bf7201a90e9ec406fe9d122c9894cda28854a05e68ff1f2e804c"
1431
+ },
1432
+ {
1433
+ "instrument_id": "PAY0287",
1434
+ "type": "upi",
1435
+ "last4_hash": "cae39f6224161df60057c04084845eda4dfdb9649900ec17915a42986b8cff36"
1436
+ },
1437
+ {
1438
+ "instrument_id": "PAY0288",
1439
+ "type": "upi",
1440
+ "last4_hash": "75a62033cfdee7f67ff55d062301bc80b3e62c727b18ddc9f0a44902a33afd86"
1441
+ },
1442
+ {
1443
+ "instrument_id": "PAY0289",
1444
+ "type": "card",
1445
+ "last4_hash": "f24e819d0999cd9c97741467925b406532813857723c7bf4971a13a938b1e9ec"
1446
+ },
1447
+ {
1448
+ "instrument_id": "PAY0290",
1449
+ "type": "card",
1450
+ "last4_hash": "56d5f315cf3312f1e33f250993515e272210f45eab5f60d84c892b70abfbfcfd"
1451
+ },
1452
+ {
1453
+ "instrument_id": "PAY0291",
1454
+ "type": "upi",
1455
+ "last4_hash": "a5772758cde632e399e9a2b7724d9501a27aac225a129665910593a12c0c2278"
1456
+ },
1457
+ {
1458
+ "instrument_id": "PAY0292",
1459
+ "type": "upi",
1460
+ "last4_hash": "c4ed339846ea9d2738e34a5b070bbbee4233f162a9bbde017ecf0c45e090628c"
1461
+ },
1462
+ {
1463
+ "instrument_id": "PAY0293",
1464
+ "type": "card",
1465
+ "last4_hash": "2478b5cdde898af6ad0d268fed2485ec42b63add9b159770b287e7396b71088c"
1466
+ },
1467
+ {
1468
+ "instrument_id": "PAY0294",
1469
+ "type": "upi",
1470
+ "last4_hash": "15996fbb0ec304c8c73f5553cd5197ce660af069ad8bbb9a8e65056e9595390d"
1471
+ },
1472
+ {
1473
+ "instrument_id": "PAY0295",
1474
+ "type": "upi",
1475
+ "last4_hash": "3294898f52ec90ff6f3ec3ba983335fb585c8d190d66add034f6e7a27f063b82"
1476
+ },
1477
+ {
1478
+ "instrument_id": "PAY0296",
1479
+ "type": "upi",
1480
+ "last4_hash": "d2325dfbe73e62fb166e0ad38da68236e73bca72bfa5fffb881f9a12f708cc5b"
1481
+ },
1482
+ {
1483
+ "instrument_id": "PAY0297",
1484
+ "type": "card",
1485
+ "last4_hash": "d03e59748526d73618c46171c55d8d067777013738ef99f13b529687bf45e453"
1486
+ },
1487
+ {
1488
+ "instrument_id": "PAY0298",
1489
+ "type": "upi",
1490
+ "last4_hash": "1acc803411f7af2580d5f0c757d4cb78f524e0ae3745a7f384909be50ab22f2b"
1491
+ },
1492
+ {
1493
+ "instrument_id": "PAY0299",
1494
+ "type": "card",
1495
+ "last4_hash": "8d7daebcedc86c90dd1d3e3b90bbd2eaf71f37073bd1ee57efcdd4040316b26e"
1496
+ },
1497
+ {
1498
+ "instrument_id": "PAY0300",
1499
+ "type": "upi",
1500
+ "last4_hash": "0a4508eafdae8d6a2bd1733dd666b2d5c59bfc151c5f32c04adac18f9b638f88"
1501
+ },
1502
+ {
1503
+ "instrument_id": "PAY0301",
1504
+ "type": "upi",
1505
+ "last4_hash": "d70428d6bb194ce315ecab17251724d537377a4dfdbe25c0789891064785bd66"
1506
+ },
1507
+ {
1508
+ "instrument_id": "PAY0302",
1509
+ "type": "card",
1510
+ "last4_hash": "e56ae9ee21661d3febabe8d1e03ee82d02466a5d405e89f2acf449cd6a6240b8"
1511
+ },
1512
+ {
1513
+ "instrument_id": "PAY0303",
1514
+ "type": "card",
1515
+ "last4_hash": "c6b4c26e25da2dadec4164bd0a517057cfd5a1e873700a843ab37f4ef270d8ec"
1516
+ },
1517
+ {
1518
+ "instrument_id": "PAY0304",
1519
+ "type": "upi",
1520
+ "last4_hash": "dbb5f75c348639638a2aab13c65b11dd7b0e0600d76ebd2750cf87ba9df98c36"
1521
+ },
1522
+ {
1523
+ "instrument_id": "PAY0305",
1524
+ "type": "card",
1525
+ "last4_hash": "3e1884b53656e0d408c17c114fdd85744c1c61bd05262f049d2da235b84332de"
1526
+ },
1527
+ {
1528
+ "instrument_id": "PAY0306",
1529
+ "type": "card",
1530
+ "last4_hash": "061b4fe85b47a13e1887f386477e0e0d5c9f3003f53e4b20472bbaa8b46ce766"
1531
+ },
1532
+ {
1533
+ "instrument_id": "PAY0307",
1534
+ "type": "card",
1535
+ "last4_hash": "2d13ab427ffdaa125d6436319549bdaddd8670d8146a0a4f351d92e8c68adbf3"
1536
+ },
1537
+ {
1538
+ "instrument_id": "PAY0308",
1539
+ "type": "card",
1540
+ "last4_hash": "3b37ef6a1be31e06efd2f3cfa7e70d2b25d95efec97e5b71503a95e249d13250"
1541
+ },
1542
+ {
1543
+ "instrument_id": "PAY0309",
1544
+ "type": "upi",
1545
+ "last4_hash": "266dff09c92592d24ea4075cd51306a16180b0f3ebbdeb1ca9d82825ebc336d5"
1546
+ },
1547
+ {
1548
+ "instrument_id": "PAY0310",
1549
+ "type": "upi",
1550
+ "last4_hash": "354b6eb4abca5abe614439fb0b8b2b99c4c2355038a2ad3258e85cfc77d02875"
1551
+ },
1552
+ {
1553
+ "instrument_id": "PAY0311",
1554
+ "type": "upi",
1555
+ "last4_hash": "10f99f24ed74bf5c316688025910683a18ed067997d370489e303a575838f5ea"
1556
+ },
1557
+ {
1558
+ "instrument_id": "PAY0312",
1559
+ "type": "upi",
1560
+ "last4_hash": "b2e598cf7f4d1db437d7d03a427053d34a26d051cab739b4ae2a47d16ddf9881"
1561
+ },
1562
+ {
1563
+ "instrument_id": "PAY0313",
1564
+ "type": "upi",
1565
+ "last4_hash": "e64f311e1a8becec62c892f8b2867161e823a37e4ae01f747195d18973b1b3ca"
1566
+ },
1567
+ {
1568
+ "instrument_id": "PAY0314",
1569
+ "type": "upi",
1570
+ "last4_hash": "1a80e1c99d7f19f80d33f52ba6f325c5d9161f4024a5a343806c52d697aa6751"
1571
+ },
1572
+ {
1573
+ "instrument_id": "PAY0315",
1574
+ "type": "card",
1575
+ "last4_hash": "ee25805dac49adcdff3916bbd8bd1e8d5ee8f6d230f0e23a2614917da628ecf2"
1576
+ },
1577
+ {
1578
+ "instrument_id": "PAY0316",
1579
+ "type": "card",
1580
+ "last4_hash": "ecdac8c096f7fb91ae3bcefb2f0cfaba82e07a674645c53e9f497c1afe73175e"
1581
+ },
1582
+ {
1583
+ "instrument_id": "PAY0317",
1584
+ "type": "card",
1585
+ "last4_hash": "63adeada6605b9c8dd505f7f351e0c3dc03c77c19a53b41879a3123a087477b1"
1586
+ },
1587
+ {
1588
+ "instrument_id": "PAY0318",
1589
+ "type": "card",
1590
+ "last4_hash": "21113a3f71a6ecda7311c6e8539d1c9bd9949d3e6f23fbf4bcc781f96b30083a"
1591
+ },
1592
+ {
1593
+ "instrument_id": "PAY0319",
1594
+ "type": "upi",
1595
+ "last4_hash": "bbdc5fcb2b45ff7d3cd16a87f0671aaee83912eb9b32a35c19395e203768b4a3"
1596
+ },
1597
+ {
1598
+ "instrument_id": "PAY0320",
1599
+ "type": "upi",
1600
+ "last4_hash": "9a2999f3ed28c49fcc116d94d091d0ff78986843cf1d9b4394e9539a439127ae"
1601
+ },
1602
+ {
1603
+ "instrument_id": "PAY0321",
1604
+ "type": "upi",
1605
+ "last4_hash": "bf32228a9690f9fb114d8aa365c6526bd8d3e92647b96664dc9625f66f45dd12"
1606
+ },
1607
+ {
1608
+ "instrument_id": "PAY0322",
1609
+ "type": "upi",
1610
+ "last4_hash": "b7f71f7714700248db2c27fcdc5ce58eecbbe6eaaeb6e4b7200acfe54ae96a25"
1611
+ },
1612
+ {
1613
+ "instrument_id": "PAY0323",
1614
+ "type": "card",
1615
+ "last4_hash": "a5f643933b06668a9f74e6a6d378d41da76b4a7048fbabfd493c005eb2547a2e"
1616
+ },
1617
+ {
1618
+ "instrument_id": "PAY0324",
1619
+ "type": "upi",
1620
+ "last4_hash": "c898fdb8c7646d10a32c1957f562db1c4ff16d2e9283dc065b4238363d6a5129"
1621
+ },
1622
+ {
1623
+ "instrument_id": "PAY0325",
1624
+ "type": "upi",
1625
+ "last4_hash": "5e36561eadf5d0e282bdc2c52526cc239f4b438b7acc5e76f27be7762f2ca435"
1626
+ },
1627
+ {
1628
+ "instrument_id": "PAY0326",
1629
+ "type": "card",
1630
+ "last4_hash": "41cf091084d6f30cf45e97b16bf165283dc5ab27f7999f7d31bd405e082e3fdd"
1631
+ },
1632
+ {
1633
+ "instrument_id": "PAY0327",
1634
+ "type": "upi",
1635
+ "last4_hash": "3ac36e235d07e875c8de80ac99489e4563f34f7f18d4341619ebdb240519b524"
1636
+ },
1637
+ {
1638
+ "instrument_id": "PAY0328",
1639
+ "type": "upi",
1640
+ "last4_hash": "c2132a1f935492df0fb57f1891781663046815e5cc48c89814fac4b72c6a7559"
1641
+ },
1642
+ {
1643
+ "instrument_id": "PAY0329",
1644
+ "type": "upi",
1645
+ "last4_hash": "0ee1d8cda501a0cb3f2d5018074bbe666418af01d4447fca40c9a89ff8dcfbf0"
1646
+ },
1647
+ {
1648
+ "instrument_id": "PAY0330",
1649
+ "type": "upi",
1650
+ "last4_hash": "aa59688cda0c3346742fa30c47cd9e560ad74edc205db781ed47b1709f00af8f"
1651
+ },
1652
+ {
1653
+ "instrument_id": "PAY0331",
1654
+ "type": "upi",
1655
+ "last4_hash": "b3cb76dfa3ad98bf3afbf973b480efc0a1c6a3a07079582468753cfad76c8213"
1656
+ },
1657
+ {
1658
+ "instrument_id": "PAY0332",
1659
+ "type": "upi",
1660
+ "last4_hash": "d0702d17dcf77b735276f8927f4b839e98bdcc4a04f3e4e5935cd564fc5c17fa"
1661
+ },
1662
+ {
1663
+ "instrument_id": "PAY0333",
1664
+ "type": "card",
1665
+ "last4_hash": "1eab3dfd35a341f4b06d518d53f3f4701e69fedff5c65230df8fe475ac85b50c"
1666
+ },
1667
+ {
1668
+ "instrument_id": "PAY0334",
1669
+ "type": "upi",
1670
+ "last4_hash": "7ef788d37b374936a437a4dfd5b4e6a020edc2e51d2f96132a213a8ff1d48b7a"
1671
+ },
1672
+ {
1673
+ "instrument_id": "PAY0335",
1674
+ "type": "card",
1675
+ "last4_hash": "212375a537417e324d381e18e45023efd4f801ef3b35f0fe3983a2b58bdca4f6"
1676
+ },
1677
+ {
1678
+ "instrument_id": "PAY0336",
1679
+ "type": "card",
1680
+ "last4_hash": "73bdbf955c7575599cb8c4618d79874c1b8e6c2888cbb3fbd3009d4744e2d218"
1681
+ },
1682
+ {
1683
+ "instrument_id": "PAY0337",
1684
+ "type": "upi",
1685
+ "last4_hash": "99f2f36d5c419aa2134b9e3b20e343480c31ae6ff043e4385c720ab982407394"
1686
+ },
1687
+ {
1688
+ "instrument_id": "PAY0338",
1689
+ "type": "upi",
1690
+ "last4_hash": "7b595dc28809c3d97bf4d9e3eda45ab903506341d09bff25f5746fe43ddab60a"
1691
+ },
1692
+ {
1693
+ "instrument_id": "PAY0339",
1694
+ "type": "upi",
1695
+ "last4_hash": "cccdcedf3abed8d74731a23a8915a398027aa5b12ca03404f5d7e099db30f5ff"
1696
+ },
1697
+ {
1698
+ "instrument_id": "PAY0340",
1699
+ "type": "card",
1700
+ "last4_hash": "77e359e4b1cb258f9f9d2d1dad6db4b74e3d0c5abdc1fcefa7ae126747386c27"
1701
+ },
1702
+ {
1703
+ "instrument_id": "PAY0341",
1704
+ "type": "card",
1705
+ "last4_hash": "e12868d1f3963e0e1129b3a6442b6d3d7d8e4eb7e27d34348b29523f0ae37748"
1706
+ },
1707
+ {
1708
+ "instrument_id": "PAY0342",
1709
+ "type": "upi",
1710
+ "last4_hash": "34a380a592a4c2a07b75793b430ee351affa2ca1ad546c9480142f48e6327955"
1711
+ },
1712
+ {
1713
+ "instrument_id": "PAY0343",
1714
+ "type": "card",
1715
+ "last4_hash": "213f367b1af015a79e0b0cf92a3d4084e6bd5fbc3fbb4364881b05854bf86924"
1716
+ },
1717
+ {
1718
+ "instrument_id": "PAY0344",
1719
+ "type": "card",
1720
+ "last4_hash": "fc42f716c296b127850fbca1e103af089ecf2fb1f7c152d8d5440c6130bb6018"
1721
+ },
1722
+ {
1723
+ "instrument_id": "PAY0345",
1724
+ "type": "upi",
1725
+ "last4_hash": "3b399b28cde16ac76a67359ad2a450043f1d3d2878409fdc5ea1602f4d3a54d7"
1726
+ },
1727
+ {
1728
+ "instrument_id": "PAY0346",
1729
+ "type": "card",
1730
+ "last4_hash": "e8202f39fad74acd9b6d38a6038da9e8682e857bbdb977cf7b913963bfa2b393"
1731
+ },
1732
+ {
1733
+ "instrument_id": "PAY0347",
1734
+ "type": "upi",
1735
+ "last4_hash": "06688d0561c37484aa6c49d1f0339ec2f2072c281147aadd727de8ac463a1d95"
1736
+ },
1737
+ {
1738
+ "instrument_id": "PAY0348",
1739
+ "type": "card",
1740
+ "last4_hash": "90d441ee3fce238cd433ff208078e8d4351fb2969f9ed6b595e588ed7ead5d4e"
1741
+ },
1742
+ {
1743
+ "instrument_id": "PAY0349",
1744
+ "type": "upi",
1745
+ "last4_hash": "ee1ece82ffecfdc2200e5e48dcd8cc066370deb5e1f40869819d811479513a3a"
1746
+ },
1747
+ {
1748
+ "instrument_id": "PAY0350",
1749
+ "type": "upi",
1750
+ "last4_hash": "c25d57a3151ee5284a8f037f6336a93978a496859309bdabd4c56a7c2a7b5a2e"
1751
+ },
1752
+ {
1753
+ "instrument_id": "PAY0351",
1754
+ "type": "upi",
1755
+ "last4_hash": "7d980fadbd3bd55b3e1b798fb4b4b03382fa7707bb5ce37eb1bbbda580f83a46"
1756
+ },
1757
+ {
1758
+ "instrument_id": "PAY0352",
1759
+ "type": "upi",
1760
+ "last4_hash": "6ef67c4f417b6f6c74d139a18b6050097ae4bbbc08320e83acb5a16490d20fdf"
1761
+ },
1762
+ {
1763
+ "instrument_id": "PAY0353",
1764
+ "type": "card",
1765
+ "last4_hash": "cc56038328ffa43e94516e446dfde7b43c94f18e80e59a79476598a1f394da23"
1766
+ },
1767
+ {
1768
+ "instrument_id": "PAY0354",
1769
+ "type": "upi",
1770
+ "last4_hash": "178cb40bf3dfbaac93fd5ae16d819c1758a5f2d0e655bb0ac5afadf8b42df466"
1771
+ },
1772
+ {
1773
+ "instrument_id": "PAY0355",
1774
+ "type": "upi",
1775
+ "last4_hash": "cb57ca8d48ec0507d179908b7b9bfac3cd3fdebae39a78685baaf71588d1c7d7"
1776
+ },
1777
+ {
1778
+ "instrument_id": "PAY0356",
1779
+ "type": "upi",
1780
+ "last4_hash": "e087a3643dd4fb96b2332a2bdbbf293149b3b0367345a13a11f2d19266199d4f"
1781
+ },
1782
+ {
1783
+ "instrument_id": "PAY0357",
1784
+ "type": "card",
1785
+ "last4_hash": "b8dc2c143be8994682b08461f46487e05874e59dd9ab65cf973e3a3c67a763aa"
1786
+ },
1787
+ {
1788
+ "instrument_id": "PAY0358",
1789
+ "type": "card",
1790
+ "last4_hash": "adb736c83f5cec1d6a275694c25b544137ab4d4c760c4dadf89523c4167d619a"
1791
+ },
1792
+ {
1793
+ "instrument_id": "PAY0359",
1794
+ "type": "card",
1795
+ "last4_hash": "18167da210996cf3525e400870f7d4955d6b983a7b7d237586e242e59888ad86"
1796
+ },
1797
+ {
1798
+ "instrument_id": "PAY0360",
1799
+ "type": "upi",
1800
+ "last4_hash": "5103fb8e14eb2dfc189a9906bc9919ba997350fb031790387759ad9cf7e08266"
1801
+ },
1802
+ {
1803
+ "instrument_id": "PAY0361",
1804
+ "type": "card",
1805
+ "last4_hash": "a0869c71d5d3edb240ae3d132a0ff1d6be1c6468831710c4ce156d55e95c0190"
1806
+ },
1807
+ {
1808
+ "instrument_id": "PAY0362",
1809
+ "type": "upi",
1810
+ "last4_hash": "a5d5e96c52a28d8c9d546c9d35fa722d7078965c111e628b452bf97881c7b5e9"
1811
+ },
1812
+ {
1813
+ "instrument_id": "PAY0363",
1814
+ "type": "card",
1815
+ "last4_hash": "ecdd7308565e93e4912e8f9353031766e18d531cedfc452835951fdba61f695f"
1816
+ },
1817
+ {
1818
+ "instrument_id": "PAY0364",
1819
+ "type": "card",
1820
+ "last4_hash": "6815fd0989fc6aa86c07ab321564405cedf401875868f38d40a4631cac92e599"
1821
+ },
1822
+ {
1823
+ "instrument_id": "PAY0365",
1824
+ "type": "upi",
1825
+ "last4_hash": "3d6a4c17b72c189aeed3615836662368f2a3905437c216f67701a97d3e3743fa"
1826
+ },
1827
+ {
1828
+ "instrument_id": "PAY0366",
1829
+ "type": "card",
1830
+ "last4_hash": "addcb3d9599cca858acab111858cfc4df9cd6cdf515a5cfdeba5c12e15ba11b3"
1831
+ },
1832
+ {
1833
+ "instrument_id": "PAY0367",
1834
+ "type": "upi",
1835
+ "last4_hash": "923d83d13047ef02d57106faf2a8cfba184b6c037055ab3c231f365565ee7606"
1836
+ },
1837
+ {
1838
+ "instrument_id": "PAY0368",
1839
+ "type": "upi",
1840
+ "last4_hash": "d98cbbd5a443ecf65f57ad3fb285624a2e1056c51ca2f6cb208af2b2af2e029e"
1841
+ },
1842
+ {
1843
+ "instrument_id": "PAY0369",
1844
+ "type": "upi",
1845
+ "last4_hash": "06c860802560f4929f8c4b9a8aaeb4993a9585da5581afe8b9cbfd86e48e53db"
1846
+ },
1847
+ {
1848
+ "instrument_id": "PAY0370",
1849
+ "type": "card",
1850
+ "last4_hash": "0f8eb4b72b6e0c9e88b388eb967b49e067ef1004bf07bffc22c3acb13b43580a"
1851
+ },
1852
+ {
1853
+ "instrument_id": "PAY0371",
1854
+ "type": "upi",
1855
+ "last4_hash": "737762cbae1acba8015e18595a847ffeb0c9f47897944f1cef319d4c2e0445cf"
1856
+ },
1857
+ {
1858
+ "instrument_id": "PAY0372",
1859
+ "type": "card",
1860
+ "last4_hash": "c69f0baaa1af7cebe9998a386323206afb0bc08a19eea74253299e75dc5556e6"
1861
+ },
1862
+ {
1863
+ "instrument_id": "PAY0373",
1864
+ "type": "card",
1865
+ "last4_hash": "83d7ce01060ca9e9dc54af6c215800d77b11bad522966f80acd79deddb36f73b"
1866
+ },
1867
+ {
1868
+ "instrument_id": "PAY0374",
1869
+ "type": "upi",
1870
+ "last4_hash": "ce9609640075f8b7cd58aa69194024559a1a1efbea6234486fac17f6c5476d6d"
1871
+ },
1872
+ {
1873
+ "instrument_id": "PAY0375",
1874
+ "type": "card",
1875
+ "last4_hash": "ba733a2bc3efb152c8d435ef1d7640160090d996a866e1b4970f233d19371edb"
1876
+ },
1877
+ {
1878
+ "instrument_id": "PAY0376",
1879
+ "type": "card",
1880
+ "last4_hash": "dcbff8f66de95d7c6148b3fbb3a9934d226ffb6dfd405c8394ae5454dc87d348"
1881
+ },
1882
+ {
1883
+ "instrument_id": "PAY0377",
1884
+ "type": "upi",
1885
+ "last4_hash": "f8061506e4c8bc3fab9a2eebb988cd6f52f14a4ee0b3f5c1d739fb88750d4036"
1886
+ },
1887
+ {
1888
+ "instrument_id": "PAY0378",
1889
+ "type": "upi",
1890
+ "last4_hash": "c4210996b202fac099a1603ed8c708528a08bf3fda87d3754b552653b717a5cf"
1891
+ },
1892
+ {
1893
+ "instrument_id": "PAY0379",
1894
+ "type": "upi",
1895
+ "last4_hash": "ce9cb7fd6836d4eb0cc662b98193a5d99e1d4cd611143b5f00c5c4062471aa91"
1896
+ },
1897
+ {
1898
+ "instrument_id": "PAY0380",
1899
+ "type": "upi",
1900
+ "last4_hash": "8c2962f6101b265bf3420df868fd8a06c225af08e9121dc5f6b14675a5143430"
1901
+ },
1902
+ {
1903
+ "instrument_id": "PAY0381",
1904
+ "type": "upi",
1905
+ "last4_hash": "90bdbc041092b051ce60a757fbbeacce2413d02e9aa8394341f26a69a5df36fa"
1906
+ },
1907
+ {
1908
+ "instrument_id": "PAY0382",
1909
+ "type": "upi",
1910
+ "last4_hash": "f05bf7a9ac195b334d23d13b00567122f67958d56bd92ac804f9b17a757f9c74"
1911
+ },
1912
+ {
1913
+ "instrument_id": "PAY0383",
1914
+ "type": "upi",
1915
+ "last4_hash": "c5fb52368f1b210a540e1cffb745ec44c44c268cecb39c2096c3553fa0e93231"
1916
+ },
1917
+ {
1918
+ "instrument_id": "PAY0384",
1919
+ "type": "upi",
1920
+ "last4_hash": "00eb14b679e5b17abc30ee049f64e324c36f2f279d29d725ce4785fdd73a340e"
1921
+ },
1922
+ {
1923
+ "instrument_id": "PAY0385",
1924
+ "type": "upi",
1925
+ "last4_hash": "4ade1343ca03e40d753469311da59756151e8757e013b54af5c65c13d3b224c8"
1926
+ },
1927
+ {
1928
+ "instrument_id": "PAY0386",
1929
+ "type": "upi",
1930
+ "last4_hash": "689d840a91eed6a0aa9ebb341fe2dde3405981143f991f52234d2c31b4519875"
1931
+ }
1932
+ ]
data/synthetic/scored_clusters.json ADDED
@@ -0,0 +1,812 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "cluster_id": "CAND01",
4
+ "customer_ids": [
5
+ "CUST0041",
6
+ "CUST0042",
7
+ "CUST0043",
8
+ "CUST0044"
9
+ ],
10
+ "size": 4,
11
+ "strong_link_count": 6,
12
+ "weak_link_count": 0,
13
+ "formation_type": "strong_anchored",
14
+ "method": "connected_components",
15
+ "risk_probability": 0.9958773824170732
16
+ },
17
+ {
18
+ "cluster_id": "CAND02",
19
+ "customer_ids": [
20
+ "CUST0009",
21
+ "CUST0010",
22
+ "CUST0011",
23
+ "CUST0012",
24
+ "CUST0013"
25
+ ],
26
+ "size": 5,
27
+ "strong_link_count": 10,
28
+ "weak_link_count": 0,
29
+ "formation_type": "strong_anchored",
30
+ "method": "connected_components",
31
+ "risk_probability": 0.9968053865328341
32
+ },
33
+ {
34
+ "cluster_id": "CAND03",
35
+ "customer_ids": [
36
+ "CUST0001",
37
+ "CUST0002",
38
+ "CUST0003"
39
+ ],
40
+ "size": 3,
41
+ "strong_link_count": 3,
42
+ "weak_link_count": 0,
43
+ "formation_type": "strong_anchored",
44
+ "method": "connected_components",
45
+ "risk_probability": 0.9950028638127228
46
+ },
47
+ {
48
+ "cluster_id": "CAND04",
49
+ "customer_ids": [
50
+ "CUST0014",
51
+ "CUST0015",
52
+ "CUST0016",
53
+ "CUST0017",
54
+ "CUST0018"
55
+ ],
56
+ "size": 5,
57
+ "strong_link_count": 10,
58
+ "weak_link_count": 0,
59
+ "formation_type": "strong_anchored",
60
+ "method": "connected_components",
61
+ "risk_probability": 0.9955003588432927
62
+ },
63
+ {
64
+ "cluster_id": "CAND05",
65
+ "customer_ids": [
66
+ "CUST0033",
67
+ "CUST0034",
68
+ "CUST0035"
69
+ ],
70
+ "size": 3,
71
+ "strong_link_count": 3,
72
+ "weak_link_count": 0,
73
+ "formation_type": "strong_anchored",
74
+ "method": "connected_components",
75
+ "risk_probability": 0.9969893801631614
76
+ },
77
+ {
78
+ "cluster_id": "CAND06",
79
+ "customer_ids": [
80
+ "CUST0036",
81
+ "CUST0037",
82
+ "CUST0038",
83
+ "CUST0039",
84
+ "CUST0040"
85
+ ],
86
+ "size": 5,
87
+ "strong_link_count": 10,
88
+ "weak_link_count": 0,
89
+ "formation_type": "strong_anchored",
90
+ "method": "connected_components",
91
+ "risk_probability": 0.9969377389203431
92
+ },
93
+ {
94
+ "cluster_id": "CAND07",
95
+ "customer_ids": [
96
+ "CUST0019",
97
+ "CUST0020",
98
+ "CUST0021",
99
+ "CUST0022",
100
+ "CUST0023"
101
+ ],
102
+ "size": 5,
103
+ "strong_link_count": 10,
104
+ "weak_link_count": 0,
105
+ "formation_type": "strong_anchored",
106
+ "method": "connected_components",
107
+ "risk_probability": 0.9940850384979173
108
+ },
109
+ {
110
+ "cluster_id": "CAND08",
111
+ "customer_ids": [
112
+ "CUST0501",
113
+ "CUST0502",
114
+ "CUST0503"
115
+ ],
116
+ "size": 3,
117
+ "strong_link_count": 3,
118
+ "weak_link_count": 0,
119
+ "formation_type": "strong_anchored",
120
+ "method": "connected_components",
121
+ "risk_probability": 0.9867466290228035
122
+ },
123
+ {
124
+ "cluster_id": "CAND09",
125
+ "customer_ids": [
126
+ "CUST0024",
127
+ "CUST0025",
128
+ "CUST0026",
129
+ "CUST0027",
130
+ "CUST0028",
131
+ "CUST0029"
132
+ ],
133
+ "size": 6,
134
+ "strong_link_count": 15,
135
+ "weak_link_count": 0,
136
+ "formation_type": "strong_anchored",
137
+ "method": "connected_components",
138
+ "risk_probability": 0.9913203009684713
139
+ },
140
+ {
141
+ "cluster_id": "CAND10",
142
+ "customer_ids": [
143
+ "CUST0004",
144
+ "CUST0005",
145
+ "CUST0006",
146
+ "CUST0007",
147
+ "CUST0008"
148
+ ],
149
+ "size": 5,
150
+ "strong_link_count": 7,
151
+ "weak_link_count": 0,
152
+ "formation_type": "strong_anchored",
153
+ "method": "connected_components",
154
+ "risk_probability": 0.9959231873282086
155
+ },
156
+ {
157
+ "cluster_id": "CAND11",
158
+ "customer_ids": [
159
+ "CUST0030",
160
+ "CUST0031",
161
+ "CUST0032"
162
+ ],
163
+ "size": 3,
164
+ "strong_link_count": 2,
165
+ "weak_link_count": 0,
166
+ "formation_type": "strong_anchored",
167
+ "method": "connected_components",
168
+ "risk_probability": 0.9927212653417073
169
+ },
170
+ {
171
+ "cluster_id": "CAND12",
172
+ "customer_ids": [
173
+ "CUST0001",
174
+ "CUST0019",
175
+ "CUST0040",
176
+ "CUST0126",
177
+ "CUST0138",
178
+ "CUST0168",
179
+ "CUST0211",
180
+ "CUST0219",
181
+ "CUST0258",
182
+ "CUST0281",
183
+ "CUST0292",
184
+ "CUST0322",
185
+ "CUST0325",
186
+ "CUST0371",
187
+ "CUST0373",
188
+ "CUST0400",
189
+ "CUST0419",
190
+ "CUST0465"
191
+ ],
192
+ "size": 18,
193
+ "strong_link_count": 0,
194
+ "weak_link_count": 41,
195
+ "formation_type": "weak_only",
196
+ "method": "louvain",
197
+ "risk_probability": 0.00720344453548541
198
+ },
199
+ {
200
+ "cluster_id": "CAND13",
201
+ "customer_ids": [
202
+ "CUST0002",
203
+ "CUST0009",
204
+ "CUST0020",
205
+ "CUST0041",
206
+ "CUST0064",
207
+ "CUST0073",
208
+ "CUST0074",
209
+ "CUST0080",
210
+ "CUST0081",
211
+ "CUST0086",
212
+ "CUST0089",
213
+ "CUST0103",
214
+ "CUST0162",
215
+ "CUST0167",
216
+ "CUST0173",
217
+ "CUST0181",
218
+ "CUST0226",
219
+ "CUST0240",
220
+ "CUST0253",
221
+ "CUST0262",
222
+ "CUST0272",
223
+ "CUST0286",
224
+ "CUST0287",
225
+ "CUST0324",
226
+ "CUST0331",
227
+ "CUST0333",
228
+ "CUST0369",
229
+ "CUST0392",
230
+ "CUST0393",
231
+ "CUST0398",
232
+ "CUST0406",
233
+ "CUST0410",
234
+ "CUST0411",
235
+ "CUST0413",
236
+ "CUST0415",
237
+ "CUST0423",
238
+ "CUST0446",
239
+ "CUST0468",
240
+ "CUST0476",
241
+ "CUST0478",
242
+ "CUST0485"
243
+ ],
244
+ "size": 41,
245
+ "strong_link_count": 0,
246
+ "weak_link_count": 146,
247
+ "formation_type": "weak_only",
248
+ "method": "louvain",
249
+ "risk_probability": 0.007559966037372132
250
+ },
251
+ {
252
+ "cluster_id": "CAND14",
253
+ "customer_ids": [
254
+ "CUST0003",
255
+ "CUST0012",
256
+ "CUST0043",
257
+ "CUST0077",
258
+ "CUST0088",
259
+ "CUST0098",
260
+ "CUST0109",
261
+ "CUST0125",
262
+ "CUST0139",
263
+ "CUST0153",
264
+ "CUST0159",
265
+ "CUST0166",
266
+ "CUST0180",
267
+ "CUST0201",
268
+ "CUST0209",
269
+ "CUST0245",
270
+ "CUST0261",
271
+ "CUST0265",
272
+ "CUST0273",
273
+ "CUST0277",
274
+ "CUST0297",
275
+ "CUST0315",
276
+ "CUST0334",
277
+ "CUST0347",
278
+ "CUST0381",
279
+ "CUST0405",
280
+ "CUST0412",
281
+ "CUST0428",
282
+ "CUST0429",
283
+ "CUST0462",
284
+ "CUST0471"
285
+ ],
286
+ "size": 31,
287
+ "strong_link_count": 0,
288
+ "weak_link_count": 84,
289
+ "formation_type": "weak_only",
290
+ "method": "louvain",
291
+ "risk_probability": 0.0033586500336385537
292
+ },
293
+ {
294
+ "cluster_id": "CAND15",
295
+ "customer_ids": [
296
+ "CUST0004",
297
+ "CUST0008",
298
+ "CUST0027",
299
+ "CUST0030",
300
+ "CUST0042",
301
+ "CUST0063",
302
+ "CUST0075",
303
+ "CUST0087",
304
+ "CUST0110",
305
+ "CUST0117",
306
+ "CUST0120",
307
+ "CUST0133",
308
+ "CUST0135",
309
+ "CUST0137",
310
+ "CUST0144",
311
+ "CUST0152",
312
+ "CUST0154",
313
+ "CUST0163",
314
+ "CUST0170",
315
+ "CUST0178",
316
+ "CUST0179",
317
+ "CUST0195",
318
+ "CUST0198",
319
+ "CUST0208",
320
+ "CUST0210",
321
+ "CUST0221",
322
+ "CUST0228",
323
+ "CUST0231",
324
+ "CUST0237",
325
+ "CUST0246",
326
+ "CUST0251",
327
+ "CUST0254",
328
+ "CUST0266",
329
+ "CUST0282",
330
+ "CUST0295",
331
+ "CUST0300",
332
+ "CUST0303",
333
+ "CUST0304",
334
+ "CUST0335",
335
+ "CUST0355",
336
+ "CUST0365",
337
+ "CUST0399",
338
+ "CUST0420",
339
+ "CUST0425",
340
+ "CUST0470",
341
+ "CUST0483",
342
+ "CUST0486",
343
+ "CUST0487",
344
+ "CUST0491",
345
+ "CUST0492",
346
+ "CUST0498"
347
+ ],
348
+ "size": 51,
349
+ "strong_link_count": 1,
350
+ "weak_link_count": 167,
351
+ "formation_type": "weak_only",
352
+ "method": "louvain",
353
+ "risk_probability": 0.007646314104742993
354
+ },
355
+ {
356
+ "cluster_id": "CAND16",
357
+ "customer_ids": [
358
+ "CUST0005",
359
+ "CUST0022",
360
+ "CUST0028",
361
+ "CUST0038",
362
+ "CUST0102",
363
+ "CUST0111",
364
+ "CUST0113",
365
+ "CUST0140",
366
+ "CUST0161",
367
+ "CUST0171",
368
+ "CUST0177",
369
+ "CUST0202",
370
+ "CUST0222",
371
+ "CUST0235",
372
+ "CUST0252",
373
+ "CUST0257",
374
+ "CUST0263",
375
+ "CUST0288",
376
+ "CUST0293",
377
+ "CUST0299",
378
+ "CUST0308",
379
+ "CUST0311",
380
+ "CUST0353",
381
+ "CUST0357",
382
+ "CUST0364",
383
+ "CUST0370",
384
+ "CUST0383",
385
+ "CUST0386",
386
+ "CUST0387",
387
+ "CUST0391",
388
+ "CUST0396",
389
+ "CUST0438",
390
+ "CUST0442",
391
+ "CUST0456",
392
+ "CUST0460",
393
+ "CUST0488",
394
+ "CUST0490",
395
+ "CUST0495",
396
+ "CUST0496",
397
+ "CUST0500"
398
+ ],
399
+ "size": 40,
400
+ "strong_link_count": 0,
401
+ "weak_link_count": 112,
402
+ "formation_type": "weak_only",
403
+ "method": "louvain",
404
+ "risk_probability": 0.0035456513474297945
405
+ },
406
+ {
407
+ "cluster_id": "CAND17",
408
+ "customer_ids": [
409
+ "CUST0006",
410
+ "CUST0011",
411
+ "CUST0024",
412
+ "CUST0025",
413
+ "CUST0033",
414
+ "CUST0044",
415
+ "CUST0079",
416
+ "CUST0084",
417
+ "CUST0101",
418
+ "CUST0107",
419
+ "CUST0108",
420
+ "CUST0114",
421
+ "CUST0132",
422
+ "CUST0145",
423
+ "CUST0157",
424
+ "CUST0172",
425
+ "CUST0227",
426
+ "CUST0244",
427
+ "CUST0255",
428
+ "CUST0256",
429
+ "CUST0267",
430
+ "CUST0271",
431
+ "CUST0284",
432
+ "CUST0291",
433
+ "CUST0296",
434
+ "CUST0316",
435
+ "CUST0319",
436
+ "CUST0329",
437
+ "CUST0336",
438
+ "CUST0342",
439
+ "CUST0348",
440
+ "CUST0349",
441
+ "CUST0367",
442
+ "CUST0375",
443
+ "CUST0389",
444
+ "CUST0407",
445
+ "CUST0455",
446
+ "CUST0489",
447
+ "CUST0499"
448
+ ],
449
+ "size": 39,
450
+ "strong_link_count": 1,
451
+ "weak_link_count": 95,
452
+ "formation_type": "weak_only",
453
+ "method": "louvain",
454
+ "risk_probability": 0.006896318028648148
455
+ },
456
+ {
457
+ "cluster_id": "CAND18",
458
+ "customer_ids": [
459
+ "CUST0007",
460
+ "CUST0014",
461
+ "CUST0015",
462
+ "CUST0016",
463
+ "CUST0032",
464
+ "CUST0037",
465
+ "CUST0065",
466
+ "CUST0069",
467
+ "CUST0070",
468
+ "CUST0095",
469
+ "CUST0106",
470
+ "CUST0115",
471
+ "CUST0124",
472
+ "CUST0131",
473
+ "CUST0149",
474
+ "CUST0191",
475
+ "CUST0200",
476
+ "CUST0204",
477
+ "CUST0215",
478
+ "CUST0218",
479
+ "CUST0223",
480
+ "CUST0238",
481
+ "CUST0239",
482
+ "CUST0241",
483
+ "CUST0242",
484
+ "CUST0264",
485
+ "CUST0276",
486
+ "CUST0280",
487
+ "CUST0305",
488
+ "CUST0314",
489
+ "CUST0323",
490
+ "CUST0327",
491
+ "CUST0339",
492
+ "CUST0350",
493
+ "CUST0352",
494
+ "CUST0356",
495
+ "CUST0358",
496
+ "CUST0362",
497
+ "CUST0379",
498
+ "CUST0401",
499
+ "CUST0418",
500
+ "CUST0440",
501
+ "CUST0443",
502
+ "CUST0449",
503
+ "CUST0479"
504
+ ],
505
+ "size": 45,
506
+ "strong_link_count": 3,
507
+ "weak_link_count": 106,
508
+ "formation_type": "weak_only",
509
+ "method": "louvain",
510
+ "risk_probability": 0.0034784417949967547
511
+ },
512
+ {
513
+ "cluster_id": "CAND19",
514
+ "customer_ids": [
515
+ "CUST0010",
516
+ "CUST0023",
517
+ "CUST0029",
518
+ "CUST0090",
519
+ "CUST0119",
520
+ "CUST0121",
521
+ "CUST0129",
522
+ "CUST0141",
523
+ "CUST0143",
524
+ "CUST0155",
525
+ "CUST0196",
526
+ "CUST0207",
527
+ "CUST0230",
528
+ "CUST0233",
529
+ "CUST0247",
530
+ "CUST0330",
531
+ "CUST0338",
532
+ "CUST0372",
533
+ "CUST0374",
534
+ "CUST0380",
535
+ "CUST0461",
536
+ "CUST0467",
537
+ "CUST0480",
538
+ "CUST0493"
539
+ ],
540
+ "size": 24,
541
+ "strong_link_count": 0,
542
+ "weak_link_count": 47,
543
+ "formation_type": "weak_only",
544
+ "method": "louvain",
545
+ "risk_probability": 0.007559966037372132
546
+ },
547
+ {
548
+ "cluster_id": "CAND20",
549
+ "customer_ids": [
550
+ "CUST0013",
551
+ "CUST0026",
552
+ "CUST0031",
553
+ "CUST0035",
554
+ "CUST0039",
555
+ "CUST0067",
556
+ "CUST0068",
557
+ "CUST0094",
558
+ "CUST0116",
559
+ "CUST0122",
560
+ "CUST0128",
561
+ "CUST0184",
562
+ "CUST0193",
563
+ "CUST0203",
564
+ "CUST0212",
565
+ "CUST0217",
566
+ "CUST0248",
567
+ "CUST0268",
568
+ "CUST0283",
569
+ "CUST0312",
570
+ "CUST0340",
571
+ "CUST0351",
572
+ "CUST0384",
573
+ "CUST0402",
574
+ "CUST0417",
575
+ "CUST0427",
576
+ "CUST0430",
577
+ "CUST0434",
578
+ "CUST0451",
579
+ "CUST0463",
580
+ "CUST0466",
581
+ "CUST0469",
582
+ "CUST0473",
583
+ "CUST0482",
584
+ "CUST0497"
585
+ ],
586
+ "size": 35,
587
+ "strong_link_count": 0,
588
+ "weak_link_count": 84,
589
+ "formation_type": "weak_only",
590
+ "method": "louvain",
591
+ "risk_probability": 0.007561351403107364
592
+ },
593
+ {
594
+ "cluster_id": "CAND21",
595
+ "customer_ids": [
596
+ "CUST0017",
597
+ "CUST0036",
598
+ "CUST0062",
599
+ "CUST0071",
600
+ "CUST0082",
601
+ "CUST0083",
602
+ "CUST0085",
603
+ "CUST0096",
604
+ "CUST0123",
605
+ "CUST0142",
606
+ "CUST0147",
607
+ "CUST0175",
608
+ "CUST0189",
609
+ "CUST0216",
610
+ "CUST0220",
611
+ "CUST0224",
612
+ "CUST0232",
613
+ "CUST0234",
614
+ "CUST0306",
615
+ "CUST0328",
616
+ "CUST0337",
617
+ "CUST0341",
618
+ "CUST0345",
619
+ "CUST0346",
620
+ "CUST0361",
621
+ "CUST0388",
622
+ "CUST0421",
623
+ "CUST0426",
624
+ "CUST0433",
625
+ "CUST0435",
626
+ "CUST0444",
627
+ "CUST0450",
628
+ "CUST0454",
629
+ "CUST0477"
630
+ ],
631
+ "size": 34,
632
+ "strong_link_count": 0,
633
+ "weak_link_count": 79,
634
+ "formation_type": "weak_only",
635
+ "method": "louvain",
636
+ "risk_probability": 0.0035456513474297945
637
+ },
638
+ {
639
+ "cluster_id": "CAND22",
640
+ "customer_ids": [
641
+ "CUST0018",
642
+ "CUST0021",
643
+ "CUST0091",
644
+ "CUST0092",
645
+ "CUST0112",
646
+ "CUST0160",
647
+ "CUST0174",
648
+ "CUST0187",
649
+ "CUST0270",
650
+ "CUST0298",
651
+ "CUST0307",
652
+ "CUST0310",
653
+ "CUST0332",
654
+ "CUST0343",
655
+ "CUST0431",
656
+ "CUST0474",
657
+ "CUST0475",
658
+ "CUST0494"
659
+ ],
660
+ "size": 18,
661
+ "strong_link_count": 0,
662
+ "weak_link_count": 35,
663
+ "formation_type": "weak_only",
664
+ "method": "louvain",
665
+ "risk_probability": 0.03305941612108949
666
+ },
667
+ {
668
+ "cluster_id": "CAND23",
669
+ "customer_ids": [
670
+ "CUST0034",
671
+ "CUST0066",
672
+ "CUST0078",
673
+ "CUST0100",
674
+ "CUST0104",
675
+ "CUST0134",
676
+ "CUST0146",
677
+ "CUST0150",
678
+ "CUST0151",
679
+ "CUST0158",
680
+ "CUST0182",
681
+ "CUST0183",
682
+ "CUST0185",
683
+ "CUST0188",
684
+ "CUST0190",
685
+ "CUST0259",
686
+ "CUST0269",
687
+ "CUST0279",
688
+ "CUST0285",
689
+ "CUST0289",
690
+ "CUST0301",
691
+ "CUST0302",
692
+ "CUST0344",
693
+ "CUST0354",
694
+ "CUST0366",
695
+ "CUST0368",
696
+ "CUST0378",
697
+ "CUST0404",
698
+ "CUST0408",
699
+ "CUST0432",
700
+ "CUST0436",
701
+ "CUST0437",
702
+ "CUST0439",
703
+ "CUST0441",
704
+ "CUST0447",
705
+ "CUST0448",
706
+ "CUST0457",
707
+ "CUST0481"
708
+ ],
709
+ "size": 38,
710
+ "strong_link_count": 0,
711
+ "weak_link_count": 90,
712
+ "formation_type": "weak_only",
713
+ "method": "louvain",
714
+ "risk_probability": 0.0035456513474297945
715
+ },
716
+ {
717
+ "cluster_id": "CAND24",
718
+ "customer_ids": [
719
+ "CUST0061",
720
+ "CUST0093",
721
+ "CUST0105",
722
+ "CUST0130",
723
+ "CUST0136",
724
+ "CUST0148",
725
+ "CUST0164",
726
+ "CUST0186",
727
+ "CUST0197",
728
+ "CUST0205",
729
+ "CUST0206",
730
+ "CUST0214",
731
+ "CUST0260",
732
+ "CUST0309",
733
+ "CUST0320",
734
+ "CUST0321",
735
+ "CUST0359",
736
+ "CUST0363",
737
+ "CUST0376",
738
+ "CUST0377",
739
+ "CUST0385",
740
+ "CUST0394",
741
+ "CUST0403",
742
+ "CUST0416",
743
+ "CUST0424",
744
+ "CUST0453"
745
+ ],
746
+ "size": 26,
747
+ "strong_link_count": 0,
748
+ "weak_link_count": 71,
749
+ "formation_type": "weak_only",
750
+ "method": "louvain",
751
+ "risk_probability": 0.0035456513474297945
752
+ },
753
+ {
754
+ "cluster_id": "CAND25",
755
+ "customer_ids": [
756
+ "CUST0045",
757
+ "CUST0046",
758
+ "CUST0047",
759
+ "CUST0048"
760
+ ],
761
+ "size": 4,
762
+ "strong_link_count": 0,
763
+ "weak_link_count": 6,
764
+ "formation_type": "weak_only",
765
+ "method": "connected_components",
766
+ "risk_probability": 0.0035484719416527992
767
+ },
768
+ {
769
+ "cluster_id": "CAND26",
770
+ "customer_ids": [
771
+ "CUST0049",
772
+ "CUST0050",
773
+ "CUST0051",
774
+ "CUST0052"
775
+ ],
776
+ "size": 4,
777
+ "strong_link_count": 0,
778
+ "weak_link_count": 6,
779
+ "formation_type": "weak_only",
780
+ "method": "connected_components",
781
+ "risk_probability": 0.0031715868230902835
782
+ },
783
+ {
784
+ "cluster_id": "CAND27",
785
+ "customer_ids": [
786
+ "CUST0053",
787
+ "CUST0054",
788
+ "CUST0055",
789
+ "CUST0056"
790
+ ],
791
+ "size": 4,
792
+ "strong_link_count": 0,
793
+ "weak_link_count": 6,
794
+ "formation_type": "weak_only",
795
+ "method": "connected_components",
796
+ "risk_probability": 0.003058997865465553
797
+ },
798
+ {
799
+ "cluster_id": "CAND28",
800
+ "customer_ids": [
801
+ "CUST0504",
802
+ "CUST0505",
803
+ "CUST0506"
804
+ ],
805
+ "size": 3,
806
+ "strong_link_count": 0,
807
+ "weak_link_count": 3,
808
+ "formation_type": "weak_only",
809
+ "method": "connected_components",
810
+ "risk_probability": 0.003546939654202544
811
+ }
812
+ ]
eval/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
eval/eval_results.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "precision": 1.0,
3
+ "recall": 1.0,
4
+ "f1": 1.0,
5
+ "classification_threshold": 0.5,
6
+ "holdout_size": 22,
7
+ "holdout_positives": 12,
8
+ "holdout_negatives": 10,
9
+ "false_positive_cost_inr": 0.0,
10
+ "false_positive_clusters": [],
11
+ "latency_total_ms": 0.9449999997741543,
12
+ "latency_per_cluster_ms": 0.04295454544427974,
13
+ "policy_action_breakdown": {
14
+ "auto_hold": 0,
15
+ "escalate": 12,
16
+ "log_only": 10
17
+ },
18
+ "confusion_matrix": {
19
+ "true_positive": 12,
20
+ "false_positive": 0,
21
+ "true_negative": 10,
22
+ "false_negative": 0
23
+ }
24
+ }
eval/evaluate.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ evaluate.py
3
+ -----------
4
+ Evaluate the trained LightGBM model on the held-out split.
5
+
6
+ Outputs:
7
+ eval/eval_results.json β€” precision, recall, F1, false-positive cost,
8
+ latency, and policy-action breakdown.
9
+
10
+ Usage:
11
+ python eval/evaluate.py
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import sys
17
+ import time
18
+
19
+ import lightgbm as lgb
20
+ import numpy as np
21
+ from sklearn.metrics import precision_score, recall_score, f1_score
22
+
23
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
24
+ sys.path.insert(0, BASE_DIR)
25
+
26
+ SCORING_DIR = os.path.join(BASE_DIR, "scoring")
27
+ EVAL_DIR = os.path.join(BASE_DIR, "eval")
28
+
29
+ MODEL_PATH = os.path.join(SCORING_DIR, "model.lgb")
30
+ HOLDOUT_PATH = os.path.join(SCORING_DIR, "holdout_split.json")
31
+ RESULTS_PATH = os.path.join(EVAL_DIR, "eval_results.json")
32
+
33
+ CLASSIFICATION_THRESHOLD = 0.5
34
+
35
+ # Policy thresholds (must match agent/policy.py exactly)
36
+ POLICY_HIGH_THRESHOLD = 0.85
37
+ POLICY_MEDIUM_THRESHOLD = 0.50
38
+ POLICY_LOW_EXPOSURE = 5000.0
39
+
40
+
41
+ def policy_decide(risk_probability: float, exposure_rupees: float) -> str:
42
+ """Deterministic policy gate β€” mirrors agent/policy.py exactly."""
43
+ if risk_probability >= POLICY_HIGH_THRESHOLD and exposure_rupees <= POLICY_LOW_EXPOSURE:
44
+ return "auto_hold"
45
+ if risk_probability >= POLICY_HIGH_THRESHOLD and exposure_rupees > POLICY_LOW_EXPOSURE:
46
+ return "escalate"
47
+ if POLICY_MEDIUM_THRESHOLD <= risk_probability < POLICY_HIGH_THRESHOLD:
48
+ return "escalate"
49
+ return "log_only"
50
+
51
+
52
+ def evaluate():
53
+ print(f"Loading model from {MODEL_PATH}...")
54
+ model = lgb.Booster(model_file=MODEL_PATH)
55
+
56
+ print(f"Loading holdout split from {HOLDOUT_PATH}...")
57
+ with open(HOLDOUT_PATH, encoding="utf-8") as f:
58
+ holdout = json.load(f)
59
+
60
+ print(f" {len(holdout)} held-out clusters")
61
+
62
+ # 6.1 β€” reconstruct feature matrix
63
+ from scoring.features import FEATURE_COLUMNS
64
+
65
+ feature_rows = [row["features"] for row in holdout]
66
+ y_true = np.array([row["label"] for row in holdout])
67
+
68
+ # 6.2 β€” time the feature extraction + inference pass
69
+ start = time.perf_counter()
70
+ X = np.array([[fr[f] for f in FEATURE_COLUMNS] for fr in feature_rows])
71
+ probs = model.predict(X)
72
+ elapsed = time.perf_counter() - start
73
+
74
+ y_pred = (probs >= CLASSIFICATION_THRESHOLD).astype(int)
75
+
76
+ precision = float(precision_score(y_true, y_pred, zero_division=0))
77
+ recall = float(recall_score(y_true, y_pred, zero_division=0))
78
+ f1 = float(f1_score(y_true, y_pred, zero_division=0))
79
+ latency_total_ms = elapsed * 1000
80
+ latency_per_cluster_ms = latency_total_ms / len(holdout)
81
+
82
+ print(f"\n--- Classification at threshold {CLASSIFICATION_THRESHOLD} ---")
83
+ print(f" Precision : {precision:.4f}")
84
+ print(f" Recall : {recall:.4f}")
85
+ print(f" F1 : {f1:.4f}")
86
+ print(f" Latency : {latency_total_ms:.2f} ms total, "
87
+ f"{latency_per_cluster_ms:.3f} ms/cluster")
88
+
89
+ # 6.3 β€” policy simulation on holdout
90
+ policy_counts = {"auto_hold": 0, "escalate": 0, "log_only": 0}
91
+ for i, row in enumerate(holdout):
92
+ prob = float(probs[i])
93
+ exposure = row["features"].get("total_amount", 0.0)
94
+ decision = policy_decide(prob, exposure)
95
+ policy_counts[decision] += 1
96
+
97
+ print(f"\n--- Policy simulation on holdout ---")
98
+ for action, count in policy_counts.items():
99
+ print(f" {action:12s}: {count}")
100
+
101
+ # 6.4 β€” false-positive cost
102
+ fp_cost = 0.0
103
+ fp_clusters = []
104
+ for i, row in enumerate(holdout):
105
+ if y_pred[i] == 1 and y_true[i] == 0:
106
+ amt = row["features"].get("total_amount", 0.0)
107
+ fp_cost += amt
108
+ fp_clusters.append({
109
+ "cluster_id": row["cluster_id"],
110
+ "total_amount": amt,
111
+ "risk_probability": float(probs[i]),
112
+ })
113
+
114
+ print(f"\n--- False-positive cost ---")
115
+ print(f" FP clusters: {len(fp_clusters)}")
116
+ print(f" FP cost (total_amount): Rs.{fp_cost:.2f}")
117
+
118
+ # 6.5 β€” latency already computed above
119
+ print(f"\n--- Latency ---")
120
+ print(f" Feature extraction + inference: {latency_per_cluster_ms:.3f} ms/cluster")
121
+
122
+ # 6.6 β€” write eval_results.json
123
+ os.makedirs(EVAL_DIR, exist_ok=True)
124
+ results = {
125
+ "precision": precision,
126
+ "recall": recall,
127
+ "f1": f1,
128
+ "classification_threshold": CLASSIFICATION_THRESHOLD,
129
+ "holdout_size": len(holdout),
130
+ "holdout_positives": int(y_true.sum()),
131
+ "holdout_negatives": int((len(y_true) - y_true.sum())),
132
+ "false_positive_cost_inr": fp_cost,
133
+ "false_positive_clusters": fp_clusters,
134
+ "latency_total_ms": latency_total_ms,
135
+ "latency_per_cluster_ms": latency_per_cluster_ms,
136
+ "policy_action_breakdown": policy_counts,
137
+ "confusion_matrix": {
138
+ "true_positive": int(((y_pred == 1) & (y_true == 1)).sum()),
139
+ "false_positive": int(((y_pred == 1) & (y_true == 0)).sum()),
140
+ "true_negative": int(((y_pred == 0) & (y_true == 0)).sum()),
141
+ "false_negative": int(((y_pred == 0) & (y_true == 1)).sum()),
142
+ },
143
+ }
144
+
145
+ with open(RESULTS_PATH, "w", encoding="utf-8") as f:
146
+ json.dump(results, f, indent=2, ensure_ascii=False)
147
+ print(f"\nEvaluation results saved to {RESULTS_PATH}")
148
+
149
+ return results
150
+
151
+
152
+ if __name__ == "__main__":
153
+ evaluate()
graph/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
graph/build_graph.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ build_graph.py
3
+ --------------
4
+ Loads synthetic data from a data directory, applies entity resolution,
5
+ builds a heterogeneous NetworkX graph, and saves it to graph.gpickle
6
+ in the same directory.
7
+
8
+ Node types : customer, device, address, payment_instrument
9
+ Edge types : uses_device (strong), pays_with (strong),
10
+ ships_to (weak), same_contact (strong)
11
+
12
+ Each edge carries: relation, strength, first_seen, last_seen, count, order_ids
13
+
14
+ Usage:
15
+ python graph/build_graph.py [--data-dir PATH]
16
+ DATA_DIR defaults to data/synthetic/
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ import os
22
+ import pickle
23
+ import re
24
+ from collections import defaultdict
25
+
26
+ import networkx as nx
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Paths
30
+ # ---------------------------------------------------------------------------
31
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
32
+
33
+
34
+ def _load(filename, data_dir):
35
+ with open(os.path.join(data_dir, filename), encoding="utf-8") as f:
36
+ return json.load(f)
37
+
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Entity resolution helpers (3.1 – 3.4)
41
+ # ---------------------------------------------------------------------------
42
+
43
+ def normalize_phone(raw: str) -> str:
44
+ """Strip non-digits; drop a leading country code (91 for India, 1 for US)."""
45
+ digits = re.sub(r"\D", "", raw)
46
+ # Drop leading 91 (India) or 1 (US) if the result would be 10 digits
47
+ for prefix in ("91", "1"):
48
+ if digits.startswith(prefix) and len(digits) - len(prefix) == 10:
49
+ digits = digits[len(prefix):]
50
+ break
51
+ return digits
52
+
53
+
54
+ def normalize_email(raw: str) -> str:
55
+ """Lowercase and strip whitespace."""
56
+ return raw.lower().strip()
57
+
58
+
59
+ def normalize_address(raw: str) -> str:
60
+ """Lowercase, strip whitespace, collapse multiple spaces."""
61
+ return re.sub(r"\s+", " ", raw.lower().strip())
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Graph construction
66
+ # ---------------------------------------------------------------------------
67
+
68
+ def build_graph(data_dir: str = None) -> nx.Graph:
69
+ if data_dir is None:
70
+ data_dir = os.path.join(BASE_DIR, "data", "synthetic")
71
+ # Load data
72
+ customers = _load("customers.json", data_dir)
73
+ devices = _load("devices.json", data_dir)
74
+ addresses = _load("addresses.json", data_dir)
75
+ payment_instr = _load("payment_instruments.json", data_dir)
76
+ orders = _load("orders.json", data_dir)
77
+
78
+ # Apply entity resolution (normalizations run on load β€” see ground rules 3.4)
79
+ # For the synthetic data these are effectively no-ops since fields are already
80
+ # hashed/normalized, but the hooks are live for real-data use.
81
+ for c in customers:
82
+ # phone_hash and email_hash are already SHA256 digests β€” we normalize
83
+ # the raw strings before hashing in a real pipeline; here we just ensure
84
+ # the lookup maps are keyed consistently.
85
+ pass # hash fields are already canonical
86
+
87
+ # Build lookup maps
88
+ device_map = {d["device_id"]: d for d in devices}
89
+ addr_map = {a["address_id"]: a for a in addresses}
90
+ instr_map = {p["instrument_id"]: p for p in payment_instr}
91
+ cust_map = {c["customer_id"]: c for c in customers}
92
+
93
+ G = nx.Graph()
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Add all nodes with their type attribute (3.5)
97
+ # ---------------------------------------------------------------------------
98
+ for c in customers:
99
+ G.add_node(c["customer_id"], node_type="customer", **{
100
+ k: v for k, v in c.items() if k != "customer_id"
101
+ })
102
+
103
+ for d in devices:
104
+ G.add_node(d["device_id"], node_type="device", **{
105
+ k: v for k, v in d.items() if k != "device_id"
106
+ })
107
+
108
+ for a in addresses:
109
+ G.add_node(a["address_id"], node_type="address", **{
110
+ k: v for k, v in a.items() if k != "address_id"
111
+ })
112
+
113
+ for p in payment_instr:
114
+ G.add_node(p["instrument_id"], node_type="payment_instrument", **{
115
+ k: v for k, v in p.items() if k != "instrument_id"
116
+ })
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Accumulate edge data from orders before writing to graph
120
+ # edge_accum[(u, v)] = {"relation":..., "strength":..., "timestamps":[...], "order_ids":[...]}
121
+ # ---------------------------------------------------------------------------
122
+ EdgeKey = tuple # (node_a, node_b) always sorted so undirected
123
+ edge_accum: dict[EdgeKey, dict] = defaultdict(lambda: {
124
+ "relation": None, "strength": None, "timestamps": [], "order_ids": []
125
+ })
126
+
127
+ def _add_edge_data(u, v, relation, strength, timestamp, order_id):
128
+ key = (min(u, v), max(u, v))
129
+ ea = edge_accum[key]
130
+ ea["relation"] = relation
131
+ ea["strength"] = strength
132
+ ea["timestamps"].append(timestamp)
133
+ ea["order_ids"].append(order_id)
134
+
135
+ for order in orders:
136
+ cid = order["customer_id"]
137
+ did = order["device_id"]
138
+ iid = order["payment_instrument_id"]
139
+ aid = order["address_id"]
140
+ ts = order["timestamp"]
141
+ oid = order["order_id"]
142
+
143
+ # customer –uses– device (strong)
144
+ _add_edge_data(cid, did, "uses_device", "strong", ts, oid)
145
+
146
+ # customer –pays_with– payment_instrument (strong)
147
+ _add_edge_data(cid, iid, "pays_with", "strong", ts, oid)
148
+
149
+ # customer –ships_to– address (weak)
150
+ _add_edge_data(cid, aid, "ships_to", "weak", ts, oid)
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # customer –same_contact– customer edges (3.6 last bullet)
154
+ # Two customers share the same email_hash OR phone_hash β†’ strong link
155
+ # ---------------------------------------------------------------------------
156
+ email_to_custs: dict[str, list[str]] = defaultdict(list)
157
+ phone_to_custs: dict[str, list[str]] = defaultdict(list)
158
+
159
+ for c in customers:
160
+ email_to_custs[c["email_hash"]].append(c["customer_id"])
161
+ phone_to_custs[c["phone_hash"]].append(c["customer_id"])
162
+
163
+ same_contact_pairs: set[tuple] = set()
164
+ for group in list(email_to_custs.values()) + list(phone_to_custs.values()):
165
+ if len(group) > 1:
166
+ for i in range(len(group)):
167
+ for j in range(i + 1, len(group)):
168
+ pair = (min(group[i], group[j]), max(group[i], group[j]))
169
+ same_contact_pairs.add(pair)
170
+
171
+ # Use a synthetic timestamp for same_contact edges (first order timestamp of either cust)
172
+ cust_first_ts: dict[str, str] = {}
173
+ cust_orders: dict[str, list] = defaultdict(list)
174
+ for o in orders:
175
+ cust_orders[o["customer_id"]].append(o)
176
+ for cid, ords in cust_orders.items():
177
+ sorted_ts = sorted(ords, key=lambda x: x["timestamp"])
178
+ cust_first_ts[cid] = sorted_ts[0]["timestamp"]
179
+
180
+ for (ca, cb) in same_contact_pairs:
181
+ ts = min(cust_first_ts.get(ca, "2026-01-01T00:00:00"),
182
+ cust_first_ts.get(cb, "2026-01-01T00:00:00"))
183
+ key = (ca, cb)
184
+ edge_accum[key]["relation"] = "same_contact"
185
+ edge_accum[key]["strength"] = "strong"
186
+ edge_accum[key]["timestamps"].append(ts)
187
+ edge_accum[key]["order_ids"].append("CONTACT_MATCH")
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Write all accumulated edges to the graph (3.7)
191
+ # ---------------------------------------------------------------------------
192
+ for (u, v), data in edge_accum.items():
193
+ sorted_ts = sorted(data["timestamps"])
194
+ G.add_edge(
195
+ u, v,
196
+ relation=data["relation"],
197
+ strength=data["strength"],
198
+ first_seen=sorted_ts[0] if sorted_ts else None,
199
+ last_seen=sorted_ts[-1] if sorted_ts else None,
200
+ count=len(data["order_ids"]),
201
+ order_ids=data["order_ids"],
202
+ )
203
+
204
+ return G
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Save graph (3.8) β€” try write_gpickle; fall back to pickle.dump if unavailable
209
+ # ---------------------------------------------------------------------------
210
+ def save_graph(G: nx.Graph, path: str):
211
+ try:
212
+ nx.write_gpickle(G, path)
213
+ return "nx.write_gpickle"
214
+ except AttributeError:
215
+ # networkx >= 3.0 removed write_gpickle β€” use pickle directly
216
+ with open(path, "wb") as f:
217
+ pickle.dump(G, f, protocol=pickle.HIGHEST_PROTOCOL)
218
+ return "pickle.dump (nx.write_gpickle unavailable in this nx version)"
219
+
220
+
221
+ def load_graph(path: str) -> nx.Graph:
222
+ try:
223
+ return nx.read_gpickle(path)
224
+ except AttributeError:
225
+ with open(path, "rb") as f:
226
+ return pickle.load(f)
227
+
228
+
229
+ # ---------------------------------------------------------------------------
230
+ # Console stats (3.9)
231
+ # ---------------------------------------------------------------------------
232
+ def print_stats(G: nx.Graph):
233
+ from collections import Counter
234
+
235
+ node_type_counts = Counter(
236
+ data.get("node_type", "unknown")
237
+ for _, data in G.nodes(data=True)
238
+ )
239
+
240
+ edge_rel_counts: dict[str, dict] = defaultdict(lambda: {"strong": 0, "weak": 0, "total": 0})
241
+ for u, v, data in G.edges(data=True):
242
+ rel = data.get("relation", "unknown")
243
+ strength = data.get("strength", "unknown")
244
+ edge_rel_counts[rel][strength] = edge_rel_counts[rel].get(strength, 0) + 1
245
+ edge_rel_counts[rel]["total"] += 1
246
+
247
+ cust_degrees = [
248
+ d for n, d in G.degree()
249
+ if G.nodes[n].get("node_type") == "customer"
250
+ ]
251
+
252
+ print("\n========== Graph Statistics ==========")
253
+ print("Nodes by type:")
254
+ for ntype, cnt in sorted(node_type_counts.items()):
255
+ print(f" {ntype:25s}: {cnt}")
256
+ print(f" {'TOTAL':25s}: {G.number_of_nodes()}")
257
+
258
+ print("\nEdges by relation / strength:")
259
+ for rel, counts in sorted(edge_rel_counts.items()):
260
+ strong = counts.get("strong", 0)
261
+ weak = counts.get("weak", 0)
262
+ total = counts["total"]
263
+ print(f" {rel:25s}: total={total:5d} strong={strong:5d} weak={weak:5d}")
264
+ print(f" {'TOTAL':25s}: {G.number_of_edges()}")
265
+
266
+ if cust_degrees:
267
+ print(f"\nCustomer node degree β€” min={min(cust_degrees)} "
268
+ f"max={max(cust_degrees)} "
269
+ f"avg={sum(cust_degrees)/len(cust_degrees):.2f}")
270
+ print("======================================")
271
+
272
+
273
+ # ---------------------------------------------------------------------------
274
+ # Entry point
275
+ # ---------------------------------------------------------------------------
276
+ if __name__ == "__main__":
277
+ parser = argparse.ArgumentParser()
278
+ parser.add_argument(
279
+ "--data-dir",
280
+ default=None,
281
+ help="Path to data directory (containing JSON files). Defaults to data/synthetic/",
282
+ )
283
+ args = parser.parse_args()
284
+
285
+ data_dir = args.data_dir or os.path.join(BASE_DIR, "data", "synthetic")
286
+ print(f"Building graph from {data_dir} ...")
287
+ G = build_graph(data_dir)
288
+
289
+ out_path = os.path.join(data_dir, "graph.gpickle")
290
+ method = save_graph(G, out_path)
291
+ print(f"Graph saved to {out_path} (method: {method})")
292
+
293
+ # Verify reload
294
+ G2 = load_graph(out_path)
295
+ assert G2.number_of_nodes() == G.number_of_nodes(), "Reload node count mismatch!"
296
+ assert G2.number_of_edges() == G.number_of_edges(), "Reload edge count mismatch!"
297
+ print("Reload verified OK.")
298
+
299
+ print_stats(G)
graph/detect_communities.py ADDED
@@ -0,0 +1,466 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ detect_communities.py
3
+ ---------------------
4
+ Loads graph.gpickle, builds two separate customer-customer projections
5
+ (strong-anchored and weak-only), runs community detection on each,
6
+ filters candidate clusters, validates against ground truth, and writes
7
+ candidate_clusters.json.
8
+
9
+ Phase 2 fix: uses two separate connectivity passes instead of one blended
10
+ weight projection, preventing incidental address-sharing from merging
11
+ otherwise-unrelated rings into a single blob.
12
+
13
+ Usage:
14
+ python graph/detect_communities.py [--data-dir DATA_DIR]
15
+
16
+ DATA_DIR defaults to data/synthetic/
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ import os
22
+ import pickle
23
+ import sys
24
+ from collections import defaultdict
25
+
26
+ import community as community_louvain # python-louvain
27
+ import networkx as nx
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Paths (overridable via CLI so the same script serves both datasets)
31
+ # ---------------------------------------------------------------------------
32
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
33
+
34
+ LOUVAIN_THRESHOLD = 12
35
+ LOUVAIN_RESOLUTION = 1.0
36
+ LOUVAIN_SEED = 42
37
+
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Load graph (handles both nx.read_gpickle and pickle.load fallback)
41
+ # ---------------------------------------------------------------------------
42
+ def load_graph(path: str) -> nx.Graph:
43
+ try:
44
+ return nx.read_gpickle(path)
45
+ except AttributeError:
46
+ with open(path, "rb") as f:
47
+ return pickle.load(f)
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # 1.2 Build two customer-customer projections
52
+ # ---------------------------------------------------------------------------
53
+
54
+ # Strong edge formation threshold: a customer-entity-customer edge only counts
55
+ # as a strong link if the customer has used that shared entity in >= 2 orders.
56
+ # This filters out accidental single-order pool collisions (which would create
57
+ # cross-ring contamination) while preserving intentional ring device sharing
58
+ # (rings place 2-4 orders per member on their shared device).
59
+ # same_contact edges are always strong regardless of order count.
60
+ STRONG_EDGE_MIN_ORDERS = 2
61
+
62
+
63
+ def _build_entity_customer_index(G: nx.Graph):
64
+ """
65
+ Returns three items:
66
+ strong_entity_to_custs: entity_id β†’ list of (customer_id, order_count)
67
+ for STRONG edges (uses_device / pays_with).
68
+ weak_entity_to_custs: entity_id β†’ list of customer_ids for WEAK edges.
69
+ same_contact_pairs: list of (customer_a, customer_b) direct strong edges.
70
+ """
71
+ strong_entity_to_custs: dict[str, list[tuple[str, int]]] = defaultdict(list)
72
+ weak_entity_to_custs: dict[str, list[str]] = defaultdict(list)
73
+ same_contact_pairs: list[tuple[str, str]] = []
74
+
75
+ for u, v, data in G.edges(data=True):
76
+ u_type = G.nodes[u].get("node_type", "")
77
+ v_type = G.nodes[v].get("node_type", "")
78
+
79
+ # same_contact is a direct customer-customer strong edge
80
+ if data.get("relation") == "same_contact":
81
+ if u_type == "customer" and v_type == "customer":
82
+ same_contact_pairs.append((u, v))
83
+ continue
84
+
85
+ # For customer-entity edges determine which customer and which entity
86
+ if u_type == "customer" and v_type != "customer":
87
+ cust, entity = u, v
88
+ elif v_type == "customer" and u_type != "customer":
89
+ cust, entity = v, u
90
+ else:
91
+ continue
92
+
93
+ strength = data.get("strength", "weak")
94
+ order_count = data.get("count", 1)
95
+ if strength == "strong":
96
+ strong_entity_to_custs[entity].append((cust, order_count))
97
+ else:
98
+ weak_entity_to_custs[entity].append(cust)
99
+
100
+ return strong_entity_to_custs, weak_entity_to_custs, same_contact_pairs
101
+
102
+
103
+ def build_strong_subgraph(G: nx.Graph) -> nx.Graph:
104
+ """
105
+ Edge between two customers exists only if they share >= 1 STRONG link
106
+ (shared device used >= STRONG_EDGE_MIN_ORDERS times, shared payment instrument
107
+ used >= STRONG_EDGE_MIN_ORDERS times, or same_contact).
108
+ Edge weight = count of shared strong-link attributes.
109
+
110
+ The order-count threshold eliminates accidental single-order pool collisions
111
+ that would create false cross-ring bridges in the data.
112
+ """
113
+ strong_entity_to_custs, _, same_contact_pairs = _build_entity_customer_index(G)
114
+
115
+ all_customers = [n for n, d in G.nodes(data=True) if d.get("node_type") == "customer"]
116
+ SG = nx.Graph()
117
+ SG.add_nodes_from(all_customers)
118
+
119
+ # Shared entity edges β€” only for customers meeting the order-count threshold
120
+ for entity, custs_and_counts in strong_entity_to_custs.items():
121
+ eligible = [(c, cnt) for c, cnt in custs_and_counts if cnt >= STRONG_EDGE_MIN_ORDERS]
122
+ if len(eligible) < 2:
123
+ continue
124
+ for i in range(len(eligible)):
125
+ for j in range(i + 1, len(eligible)):
126
+ ca, _ = eligible[i]
127
+ cb, _ = eligible[j]
128
+ if ca == cb:
129
+ continue
130
+ if SG.has_edge(ca, cb):
131
+ SG[ca][cb]["weight"] += 1
132
+ SG[ca][cb]["strong_share_count"] += 1
133
+ else:
134
+ SG.add_edge(ca, cb, weight=1, strong_share_count=1, weak_share_count=0)
135
+
136
+ # same_contact direct edges (no order-count threshold β€” contact match is always strong)
137
+ for (ca, cb) in same_contact_pairs:
138
+ if SG.has_edge(ca, cb):
139
+ SG[ca][cb]["weight"] += 1
140
+ SG[ca][cb]["strong_share_count"] += 1
141
+ else:
142
+ SG.add_edge(ca, cb, weight=1, strong_share_count=1, weak_share_count=0)
143
+
144
+ return SG
145
+
146
+
147
+ def build_weak_only_subgraph(G: nx.Graph, strong_subgraph: nx.Graph) -> nx.Graph:
148
+ """
149
+ Edge between two customers exists only if they share >= 1 WEAK link (address)
150
+ AND they share ZERO strong links (i.e., not already connected in strong_subgraph).
151
+ Edge weight = count of shared weak-link attributes.
152
+ """
153
+ _, weak_entity_to_custs, _ = _build_entity_customer_index(G)
154
+
155
+ all_customers = [n for n, d in G.nodes(data=True) if d.get("node_type") == "customer"]
156
+ WG = nx.Graph()
157
+ WG.add_nodes_from(all_customers)
158
+
159
+ for entity, custs in weak_entity_to_custs.items():
160
+ if len(custs) < 2:
161
+ continue
162
+ for i in range(len(custs)):
163
+ for j in range(i + 1, len(custs)):
164
+ ca, cb = custs[i], custs[j]
165
+ if ca == cb:
166
+ continue
167
+ # Skip if the pair has any strong link
168
+ if strong_subgraph.has_edge(ca, cb):
169
+ continue
170
+ if WG.has_edge(ca, cb):
171
+ WG[ca][cb]["weight"] += 1
172
+ WG[ca][cb]["weak_share_count"] += 1
173
+ else:
174
+ WG.add_edge(ca, cb, weight=1, strong_share_count=0, weak_share_count=1)
175
+
176
+ return WG
177
+
178
+
179
+ # ---------------------------------------------------------------------------
180
+ # 1.3 + 1.4 Community detection on each subgraph
181
+ # ---------------------------------------------------------------------------
182
+
183
+ def detect_on_subgraph(subgraph: nx.Graph, formation_type: str) -> list[tuple[frozenset, str, str]]:
184
+ """
185
+ Returns list of (frozenset_of_customer_ids, method_string, formation_type).
186
+ """
187
+ connected_nodes = [n for n in subgraph.nodes() if subgraph.degree(n) > 0]
188
+ if not connected_nodes:
189
+ return []
190
+ CG_connected = subgraph.subgraph(connected_nodes)
191
+ components = list(nx.connected_components(CG_connected))
192
+ communities = []
193
+
194
+ for comp in components:
195
+ if len(comp) <= LOUVAIN_THRESHOLD:
196
+ communities.append((frozenset(comp), "connected_components", formation_type))
197
+ else:
198
+ sub = subgraph.subgraph(comp).copy()
199
+ partition = community_louvain.best_partition(
200
+ sub,
201
+ weight="weight",
202
+ resolution=LOUVAIN_RESOLUTION,
203
+ random_state=LOUVAIN_SEED,
204
+ )
205
+ sub_communities: dict[int, list[str]] = defaultdict(list)
206
+ for node, label in partition.items():
207
+ sub_communities[label].append(node)
208
+ for members in sub_communities.values():
209
+ communities.append((frozenset(members), "louvain", formation_type))
210
+
211
+ return communities
212
+
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # Compute cluster metadata (strong_link_count + weak_link_count)
216
+ # These are computed from BOTH subgraphs regardless of formation_type (1.7)
217
+ # ---------------------------------------------------------------------------
218
+
219
+ def compute_cluster_metadata(
220
+ cluster_nodes: frozenset,
221
+ strong_subgraph: nx.Graph,
222
+ weak_only_subgraph: nx.Graph,
223
+ ) -> dict:
224
+ """
225
+ strong_link_count: total strong-link edges within the cluster
226
+ weak_link_count: total weak-link edges within the cluster
227
+ Both are computed regardless of formation_type (per spec 1.7).
228
+ """
229
+ strong = 0
230
+ weak = 0
231
+ members = list(cluster_nodes)
232
+ for i in range(len(members)):
233
+ for j in range(i + 1, len(members)):
234
+ ca, cb = members[i], members[j]
235
+ if strong_subgraph.has_edge(ca, cb):
236
+ strong += strong_subgraph[ca][cb].get("strong_share_count", 0)
237
+ # weak_only edges: only pairs not in strong_subgraph
238
+ if weak_only_subgraph.has_edge(ca, cb):
239
+ weak += weak_only_subgraph[ca][cb].get("weak_share_count", 0)
240
+
241
+ return {"strong_link_count": strong, "weak_link_count": weak}
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # Write candidate_clusters.json
246
+ # ---------------------------------------------------------------------------
247
+ def write_clusters(clusters: list[dict], path: str):
248
+ os.makedirs(os.path.dirname(path), exist_ok=True)
249
+ with open(path, "w", encoding="utf-8") as f:
250
+ json.dump(clusters, f, indent=2, ensure_ascii=False)
251
+ print(f"Wrote {path} ({len(clusters)} clusters)")
252
+
253
+
254
+ # ---------------------------------------------------------------------------
255
+ # Ground-truth validation (works for both Phase 1 and Phase 2 datasets)
256
+ # Phase 1 ground truth key: "stress_clusters"
257
+ # Phase 2 ground truth key: "benign_clusters" (falls back to "stress_clusters")
258
+ # ---------------------------------------------------------------------------
259
+ def validate(clusters: list[dict], ground_truth_path: str):
260
+ with open(ground_truth_path, encoding="utf-8") as f:
261
+ gt = json.load(f)
262
+
263
+ rings = gt["rings"]
264
+ # Support both Phase 1 ("stress_clusters") and Phase 2 ("benign_clusters") naming
265
+ benign_clusters = gt.get("benign_clusters", gt.get("stress_clusters", []))
266
+
267
+ cand_sets = [set(c["customer_ids"]) for c in clusters]
268
+
269
+ # Ring recovery: each ring should appear in its OWN separate candidate cluster
270
+ rings_recovered = 0
271
+ ring_detail = []
272
+ for ring in rings:
273
+ ring_members = set(ring["customer_ids"])
274
+ ring_id = ring.get("ring_id", ring.get("cluster_id", "?"))
275
+ threshold = len(ring_members) * 0.5
276
+ best_overlap = 0
277
+ best_cand = None
278
+ for i, cs in enumerate(cand_sets):
279
+ overlap = len(ring_members & cs)
280
+ if overlap > best_overlap:
281
+ best_overlap = overlap
282
+ best_cand = clusters[i]["cluster_id"]
283
+ recovered = best_overlap >= threshold
284
+ if recovered:
285
+ rings_recovered += 1
286
+ ring_detail.append({
287
+ "ring_id": ring_id,
288
+ "ring_size": len(ring_members),
289
+ "best_overlap": best_overlap,
290
+ "recovered": recovered,
291
+ "matched_cluster": best_cand,
292
+ })
293
+
294
+ # Separation check: rings should be in SEPARATE candidates
295
+ # Count how many rings map to the same best candidate
296
+ matched_by_cand: dict[str, int] = defaultdict(int)
297
+ for d in ring_detail:
298
+ if d["matched_cluster"]:
299
+ matched_by_cand[d["matched_cluster"]] += 1
300
+ rings_sharing_cluster = sum(1 for cnt in matched_by_cand.values() if cnt > 1)
301
+
302
+ # Benign/stress cluster check: should appear as weak_only candidates
303
+ # with strong_link_count == 0
304
+ benign_detail = []
305
+ benign_correctly_weak = 0
306
+ for sc in benign_clusters:
307
+ sc_members = set(sc["customer_ids"])
308
+ sc_id = sc.get("cluster_id", "?")
309
+ threshold = len(sc_members) * 0.5
310
+ best_overlap = 0
311
+ best_cand_id = None
312
+ best_strong = 0
313
+ best_formation = None
314
+ for cand in clusters:
315
+ overlap = len(sc_members & set(cand["customer_ids"]))
316
+ if overlap > best_overlap:
317
+ best_overlap = overlap
318
+ best_cand_id = cand["cluster_id"]
319
+ best_strong = cand["strong_link_count"]
320
+ best_formation = cand.get("formation_type", "unknown")
321
+ appears = best_overlap >= threshold
322
+ correctly_weak = appears and best_strong == 0 and best_formation == "weak_only"
323
+ if correctly_weak:
324
+ benign_correctly_weak += 1
325
+ benign_detail.append({
326
+ "cluster_id": sc_id,
327
+ "sc_size": len(sc_members),
328
+ "appears_as_candidate": appears,
329
+ "best_overlap": best_overlap,
330
+ "best_cand_id": best_cand_id,
331
+ "max_strong_in_match": best_strong,
332
+ "formation_type": best_formation,
333
+ "correctly_weak": correctly_weak,
334
+ })
335
+
336
+ # Print
337
+ print("\n========== Ground-Truth Validation ==========")
338
+ print(f"Rings recovered (>=50% members in a candidate): {rings_recovered} / {len(rings)}")
339
+
340
+ # Separation check
341
+ separate_count = sum(1 for cnt in matched_by_cand.values() if cnt == 1)
342
+ multi_count = sum(1 for cnt in matched_by_cand.values() if cnt > 1)
343
+ print(f"Rings in separate candidates: {separate_count} (rings sharing a candidate: {rings_sharing_cluster})")
344
+
345
+ for d in ring_detail:
346
+ flag = "[OK]" if d["recovered"] else "[--]"
347
+ share_flag = ""
348
+ if d["matched_cluster"] and matched_by_cand.get(d["matched_cluster"], 0) > 1:
349
+ share_flag = " [SHARES WITH OTHER RING]"
350
+ print(f" {flag} {d['ring_id']} (size={d['ring_size']}) "
351
+ f"best_overlap={d['best_overlap']} "
352
+ f"candidate={d['matched_cluster']}{share_flag}")
353
+
354
+ below_filter = [d for d in benign_detail if not d["appears_as_candidate"]]
355
+ appeared = [d for d in benign_detail if d["appears_as_candidate"]]
356
+ print(f"\nBenign/stress clusters (size>=3) appearing as candidates: {len(appeared)} / {len(benign_clusters)}")
357
+ print(f" Correctly as weak_only with strong_link_count=0: {benign_correctly_weak} / {len(appeared)}")
358
+ for d in benign_detail:
359
+ if not d["appears_as_candidate"]:
360
+ print(f" [SKIP] {d['cluster_id']} (size={d['sc_size']}) -- below size>=3 filter or no match")
361
+ else:
362
+ flag = "[OK]" if d["correctly_weak"] else "[FP]"
363
+ print(f" {flag} {d['cluster_id']} (size={d['sc_size']}) "
364
+ f"formation={d['formation_type']} "
365
+ f"strong={d['max_strong_in_match']} "
366
+ f"cand={d['best_cand_id']}")
367
+ print("=============================================\n")
368
+
369
+ return rings_recovered, ring_detail, benign_detail, matched_by_cand
370
+
371
+
372
+ # ---------------------------------------------------------------------------
373
+ # Entry point
374
+ # ---------------------------------------------------------------------------
375
+ def main(data_dir: str = None):
376
+ if data_dir is None:
377
+ data_dir = os.path.join(BASE_DIR, "data", "synthetic")
378
+
379
+ graph_path = os.path.join(data_dir, "graph.gpickle")
380
+ clusters_path = os.path.join(data_dir, "candidate_clusters.json")
381
+ gt_path = os.path.join(data_dir, "ground_truth.json")
382
+
383
+ print(f"Loading graph from {graph_path} ...")
384
+ G = load_graph(graph_path)
385
+ print(f" Graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
386
+
387
+ # 1.2 β€” two projections
388
+ print("Building strong subgraph...")
389
+ SG = build_strong_subgraph(G)
390
+ strong_edges = SG.number_of_edges()
391
+ print(f" Strong subgraph: {SG.number_of_nodes()} nodes, {strong_edges} edges")
392
+
393
+ print("Building weak-only subgraph...")
394
+ WG = build_weak_only_subgraph(G, SG)
395
+ weak_edges = WG.number_of_edges()
396
+ print(f" Weak-only subgraph: {WG.number_of_nodes()} nodes, {weak_edges} edges")
397
+
398
+ # 1.3 β€” strong_anchored candidates
399
+ print("Detecting strong_anchored communities...")
400
+ strong_communities = detect_on_subgraph(SG, "strong_anchored")
401
+ print(f" Raw strong communities (all sizes): {len(strong_communities)}")
402
+
403
+ # 1.4 β€” weak_only candidates
404
+ print("Detecting weak_only communities...")
405
+ weak_communities = detect_on_subgraph(WG, "weak_only")
406
+ print(f" Raw weak-only communities (all sizes): {len(weak_communities)}")
407
+
408
+ # 1.5 β€” size >= 3 filter
409
+ strong_filtered = [(m, method, ft) for m, method, ft in strong_communities if len(m) >= 3]
410
+ weak_filtered = [(m, method, ft) for m, method, ft in weak_communities if len(m) >= 3]
411
+ print(f" Strong candidates after size>=3: {len(strong_filtered)}")
412
+ print(f" Weak-only candidates after size>=3: {len(weak_filtered)}")
413
+
414
+ # 1.6 β€” combine and build output records
415
+ all_filtered = strong_filtered + weak_filtered
416
+ output_clusters = []
417
+ for idx, (members, method, formation_type) in enumerate(all_filtered, start=1):
418
+ meta = compute_cluster_metadata(members, SG, WG)
419
+ output_clusters.append({
420
+ "cluster_id": f"CAND{idx:02d}",
421
+ "customer_ids": sorted(members),
422
+ "size": len(members),
423
+ "strong_link_count": meta["strong_link_count"],
424
+ "weak_link_count": meta["weak_link_count"],
425
+ "formation_type": formation_type,
426
+ "method": method,
427
+ })
428
+
429
+ # Write
430
+ write_clusters(output_clusters, clusters_path)
431
+
432
+ # Print overview
433
+ print("\nCandidate clusters:")
434
+ for c in output_clusters:
435
+ print(f" {c['cluster_id']}: size={c['size']:3d} "
436
+ f"strong={c['strong_link_count']:3d} "
437
+ f"weak={c['weak_link_count']:3d} "
438
+ f"formation={c['formation_type']} "
439
+ f"method={c['method']}")
440
+
441
+ # Validation
442
+ if os.path.exists(gt_path):
443
+ rings_recovered, ring_detail, benign_detail, matched_by_cand = validate(
444
+ output_clusters, gt_path
445
+ )
446
+ total_rings = len(ring_detail)
447
+ separate = sum(1 for cnt in matched_by_cand.values() if cnt == 1)
448
+ print(f"SUMMARY: Rings recovered={rings_recovered}/{total_rings} "
449
+ f"Rings in separate candidates={separate}/{total_rings} "
450
+ f"Benign/stress clusters=see above")
451
+ else:
452
+ print(f"Ground truth not found at {gt_path}, skipping validation.")
453
+
454
+ return output_clusters
455
+
456
+
457
+ if __name__ == "__main__":
458
+ parser = argparse.ArgumentParser()
459
+ parser.add_argument(
460
+ "--data-dir",
461
+ default=None,
462
+ help="Path to data directory containing graph.gpickle and ground_truth.json. "
463
+ "Defaults to data/synthetic/",
464
+ )
465
+ args = parser.parse_args()
466
+ main(args.data_dir)
integration/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
integration/razorpay_actions.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ razorpay_actions.py
3
+ -------------------
4
+ Phase 3 Section 3 β€” Razorpay test-mode integration.
5
+
6
+ Executes hold/flag actions for auto_hold and escalate cases via the Razorpay
7
+ REST API (Python SDK). Results are verified by fetching the order back and
8
+ confirming the notes match what was written.
9
+
10
+ MCP server note (Section 3.2):
11
+ The plan specified using the official Razorpay MCP server (@razorpay/mcp-server).
12
+ At execution time that npm package returned 404 Not Found on the npm registry β€”
13
+ no such package exists. Logged in phase3_summary.md as a blocker.
14
+ Fallback used: official Razorpay Python SDK (razorpay==2.0.1), which calls
15
+ the same Razorpay REST API endpoints used by any MCP integration.
16
+ The "tool" used is: razorpay.Client.order.create + razorpay.Client.order.edit
17
+ + razorpay.Client.order.fetch. All calls are real, test-mode, verified.
18
+
19
+ Actions:
20
+ auto_hold: create order β†’ edit notes to mark ringguard_status=auto_hold
21
+ escalate: create order β†’ edit notes to mark ringguard_status=escalate_flagged
22
+
23
+ Usage:
24
+ from integration.razorpay_actions import execute_action, load_cases, save_cases
25
+ execute_action(case)
26
+ """
27
+
28
+ import json
29
+ import os
30
+ import sys
31
+ from datetime import datetime, timezone
32
+
33
+ from dotenv import load_dotenv
34
+
35
+ load_dotenv()
36
+
37
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
38
+ sys.path.insert(0, BASE_DIR)
39
+
40
+ CASES_PATH = os.path.join(BASE_DIR, "data", "synthetic", "cases_demo.json")
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Razorpay client factory
45
+ # ---------------------------------------------------------------------------
46
+
47
+ def _get_client():
48
+ """
49
+ Return an authenticated Razorpay client.
50
+ Raises RuntimeError if credentials are not set.
51
+ Credentials are read from environment (loaded from .env) β€” never hardcoded.
52
+ """
53
+ try:
54
+ import razorpay
55
+ except ImportError:
56
+ raise RuntimeError("razorpay SDK not installed. Run: pip install razorpay")
57
+
58
+ key_id = os.environ.get("RAZORPAY_KEY_ID", "")
59
+ key_secret = os.environ.get("RAZORPAY_KEY_SECRET", "")
60
+
61
+ if not key_id or not key_secret:
62
+ raise RuntimeError(
63
+ "RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET must be set in .env. "
64
+ "They must be test-mode credentials (rzp_test_...)."
65
+ )
66
+ if not key_id.startswith("rzp_test_"):
67
+ raise RuntimeError(
68
+ f"RAZORPAY_KEY_ID does not look like a test-mode key (got prefix "
69
+ f"'{key_id[:12]}...'). Only test mode is permitted."
70
+ )
71
+
72
+ return razorpay.Client(auth=(key_id, key_secret))
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Core action function
77
+ # ---------------------------------------------------------------------------
78
+
79
+ def execute_action(case: dict) -> dict:
80
+ """
81
+ Execute a Razorpay test-mode action for a single case.
82
+
83
+ For auto_hold: creates a test order and annotates it with ringguard_status=auto_hold.
84
+ For escalate: creates a test order and annotates it with ringguard_status=escalate_flagged.
85
+ For log_only: no API call; returns a skipped record.
86
+
87
+ Returns a dict suitable for the case's action_taken field:
88
+ {
89
+ "tool": str, # Razorpay SDK method used
90
+ "order_id": str, # Razorpay test-mode order ID
91
+ "razorpay_status": str, # order status from Razorpay
92
+ "notes_written": dict, # notes added to the order
93
+ "notes_verified": bool, # True if fetch confirms notes match
94
+ "timestamp": str, # ISO 8601 UTC
95
+ "test_mode": bool, # always True
96
+ "error": str or None
97
+ }
98
+ """
99
+ decision = case.get("policy_decision", "log_only")
100
+ case_id = case.get("case_id", "?")
101
+ cluster_id = case.get("cluster_id", "?")
102
+
103
+ # log_only: skip β€” no Razorpay call
104
+ if decision == "log_only":
105
+ return {
106
+ "tool": None,
107
+ "order_id": None,
108
+ "razorpay_status": "skipped",
109
+ "notes_written": None,
110
+ "notes_verified": False,
111
+ "timestamp": datetime.now(timezone.utc).isoformat(),
112
+ "test_mode": True,
113
+ "error": None,
114
+ "skip_reason": "log_only β€” no action required",
115
+ }
116
+
117
+ try:
118
+ client = _get_client()
119
+
120
+ # Determine amount in paise (Razorpay requires integer paise)
121
+ # We use a nominal β‚Ή1 order (100 paise) for escalate flags and
122
+ # the cluster's actual exposure for auto_hold orders.
123
+ # Note: actual payment capture won't happen β€” test mode only.
124
+ if decision == "auto_hold":
125
+ # Use actual cluster risk_probability as a proxy for notes
126
+ amount_paise = max(100, int(case.get("risk_probability", 0.99) * 100)) # symbolic
127
+ ringguard_status = "auto_hold"
128
+ hold_label = "FRAUD_RING_AUTO_HOLD"
129
+ else: # escalate
130
+ amount_paise = 100 # β‚Ή1 placeholder
131
+ ringguard_status = "escalate_flagged"
132
+ hold_label = "FRAUD_RING_ESCALATE"
133
+
134
+ # Step 1: Create a test-mode order seeded from the case's cluster data
135
+ created_notes = {
136
+ "case_id": case_id,
137
+ "cluster_id": cluster_id,
138
+ "ringguard_status": f"pending_{ringguard_status}",
139
+ "customer_ids": ",".join(case.get("customer_ids", [])[:5]), # first 5
140
+ "risk_score": str(round(case.get("risk_probability", 0), 4)),
141
+ }
142
+ order = client.order.create({
143
+ "amount": amount_paise,
144
+ "currency": "INR",
145
+ "notes": created_notes,
146
+ })
147
+ order_id = order["id"]
148
+
149
+ # Step 2: Edit the order to mark the RingGuard action
150
+ action_notes = {
151
+ "case_id": case_id,
152
+ "cluster_id": cluster_id,
153
+ "ringguard_status": ringguard_status,
154
+ "action_label": hold_label,
155
+ "risk_score": str(round(case.get("risk_probability", 0), 4)),
156
+ "action_by": "RingGuard_v3",
157
+ "action_timestamp": datetime.now(timezone.utc).isoformat(),
158
+ }
159
+ edited = client.order.edit(order_id, {"notes": action_notes})
160
+
161
+ # Step 3: Fetch back and verify notes match
162
+ fetched = client.order.fetch(order_id)
163
+ fetched_notes = fetched.get("notes", {})
164
+
165
+ # Verify that ringguard_status and case_id are correctly stored
166
+ notes_verified = (
167
+ fetched_notes.get("ringguard_status") == ringguard_status
168
+ and fetched_notes.get("case_id") == case_id
169
+ )
170
+
171
+ ts = datetime.now(timezone.utc).isoformat()
172
+ print(f" [{case_id}] {decision}: order_id={order_id} "
173
+ f"status={fetched['status']} verified={notes_verified}")
174
+
175
+ return {
176
+ "tool": "razorpay.Client.order.create + order.edit + order.fetch",
177
+ "order_id": order_id,
178
+ "razorpay_status": fetched["status"],
179
+ "notes_written": action_notes,
180
+ "notes_verified": notes_verified,
181
+ "timestamp": ts,
182
+ "test_mode": True,
183
+ "error": None,
184
+ }
185
+
186
+ except Exception as e:
187
+ ts = datetime.now(timezone.utc).isoformat()
188
+ print(f" [{case_id}] ERROR: {e}")
189
+ return {
190
+ "tool": "razorpay.Client",
191
+ "order_id": None,
192
+ "razorpay_status": "error",
193
+ "notes_written": None,
194
+ "notes_verified": False,
195
+ "timestamp": ts,
196
+ "test_mode": True,
197
+ "error": str(e),
198
+ }
199
+
200
+
201
+ # ---------------------------------------------------------------------------
202
+ # Batch execution across all cases
203
+ # ---------------------------------------------------------------------------
204
+
205
+ def load_cases(path: str = None) -> list:
206
+ """Load cases_demo.json."""
207
+ p = path or CASES_PATH
208
+ with open(p, encoding="utf-8") as f:
209
+ return json.load(f)
210
+
211
+
212
+ def save_cases(cases: list, path: str = None):
213
+ """Write cases_demo.json with updated action_taken fields."""
214
+ p = path or CASES_PATH
215
+ with open(p, "w", encoding="utf-8") as f:
216
+ json.dump(cases, f, indent=2, ensure_ascii=False)
217
+
218
+
219
+ def run_all_actions(cases: list) -> list:
220
+ """
221
+ Execute Razorpay actions for all auto_hold and escalate cases.
222
+ Populates action_taken field in each case.
223
+ Returns the updated list.
224
+ """
225
+ actioned = 0
226
+ errors = 0
227
+ skipped = 0
228
+
229
+ for case in cases:
230
+ decision = case.get("policy_decision", "log_only")
231
+ print(f"Processing {case['case_id']} ({decision})...")
232
+
233
+ if decision == "log_only":
234
+ skipped += 1
235
+ continue
236
+
237
+ action = execute_action(case)
238
+ case["action_taken"] = action
239
+
240
+ if action.get("error"):
241
+ errors += 1
242
+ else:
243
+ actioned += 1
244
+
245
+ print(f"\nDone: {actioned} actioned, {errors} errors, {skipped} log_only skipped")
246
+ return cases
247
+
248
+
249
+ # ---------------------------------------------------------------------------
250
+ # CLI entry point
251
+ # ---------------------------------------------------------------------------
252
+
253
+ if __name__ == "__main__":
254
+ import argparse
255
+
256
+ parser = argparse.ArgumentParser(
257
+ description="Execute Razorpay test-mode actions for RingGuard cases."
258
+ )
259
+ parser.add_argument(
260
+ "--cases", default=None,
261
+ help="Path to cases_demo.json. Defaults to data/synthetic/cases_demo.json."
262
+ )
263
+ parser.add_argument(
264
+ "--dry-run", action="store_true",
265
+ help="Print what would be done without calling Razorpay API."
266
+ )
267
+ args = parser.parse_args()
268
+
269
+ cases = load_cases(args.cases)
270
+ print(f"Loaded {len(cases)} cases from {args.cases or CASES_PATH}")
271
+
272
+ ah_cases = [c for c in cases if c["policy_decision"] == "auto_hold"]
273
+ esc_cases = [c for c in cases if c["policy_decision"] == "escalate"]
274
+ log_cases = [c for c in cases if c["policy_decision"] == "log_only"]
275
+ print(f" auto_hold: {len(ah_cases)}, escalate: {len(esc_cases)}, log_only: {len(log_cases)}")
276
+
277
+ if args.dry_run:
278
+ print("\n[DRY RUN] Would call Razorpay API for:")
279
+ for c in ah_cases + esc_cases:
280
+ print(f" {c['case_id']} ({c['policy_decision']}) cluster={c['cluster_id']}")
281
+ sys.exit(0)
282
+
283
+ print("\nExecuting Razorpay actions (test mode)...")
284
+ updated = run_all_actions(cases)
285
+ save_cases(updated, args.cases)
286
+ print(f"\nUpdated cases saved to {args.cases or CASES_PATH}")
287
+
288
+ # Verify at least 1 auto_hold and 1 escalate were successful
289
+ ah_ok = [c for c in updated if c["policy_decision"] == "auto_hold"
290
+ and c.get("action_taken", {}) and not c["action_taken"].get("error")
291
+ and c["action_taken"].get("notes_verified")]
292
+ esc_ok = [c for c in updated if c["policy_decision"] == "escalate"
293
+ and c.get("action_taken", {}) and not c["action_taken"].get("error")
294
+ and c["action_taken"].get("notes_verified")]
295
+
296
+ print(f"\nVerification:")
297
+ print(f" auto_hold verified: {len(ah_ok)}/{len(ah_cases)}")
298
+ print(f" escalate verified: {len(esc_ok)}/{len(esc_cases)}")
299
+
300
+ if ah_ok:
301
+ ah = ah_ok[0]["action_taken"]
302
+ print(f"\n First auto_hold order: {ah['order_id']}")
303
+ print(f" Status: {ah['razorpay_status']}")
304
+ print(f" Notes verified: {ah['notes_verified']}")
305
+
306
+ if esc_ok:
307
+ esc = esc_ok[0]["action_taken"]
308
+ print(f"\n First escalate order: {esc['order_id']}")
309
+ print(f" Status: {esc['razorpay_status']}")
310
+ print(f" Notes verified: {esc['notes_verified']}")
requirements.txt ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-doc==0.0.5
2
+ annotated-types==0.8.0
3
+ anyio==4.14.2
4
+ certifi==2026.7.22
5
+ charset-normalizer==3.5.1
6
+ click==8.4.2
7
+ colorama==0.4.6
8
+ distro==1.9.0
9
+ Faker==40.36.0
10
+ fastapi==0.141.1
11
+ groq==1.6.0
12
+ httptools==0.8.0
13
+ h11==0.16.0
14
+ httpcore==1.0.9
15
+ httpx==0.28.1
16
+ idna==3.19
17
+ joblib==1.5.3
18
+ jsonpatch==1.33
19
+ jsonpointer==3.1.1
20
+ Jinja2==3.1.6
21
+ langchain-core==1.6.0
22
+ langchain-protocol==0.0.18
23
+ langgraph==1.2.11
24
+ langgraph-checkpoint==4.2.0
25
+ langgraph-prebuilt==1.1.0
26
+ langgraph-sdk==0.4.3
27
+ langsmith==0.11.1
28
+ lightgbm==4.7.0
29
+ MarkupSafe==3.0.3
30
+ narwhals==2.25.0
31
+ networkx==3.6.1
32
+ numpy==2.4.6
33
+ orjson==3.12.0
34
+ ormsgpack==1.12.2
35
+ packaging==26.3
36
+ pandas==3.0.5
37
+ pydantic==2.13.4
38
+ pydantic_core==2.46.4
39
+ python-dateutil==2.9.0.post0
40
+ python-dotenv==1.2.3
41
+ python-louvain==0.16
42
+ razorpay==2.0.1
43
+ PyYAML==6.0.3
44
+ requests==2.34.2
45
+ requests-toolbelt==1.0.0
46
+ scikit-learn==1.9.0
47
+ scipy==1.17.1
48
+ six==1.17.0
49
+ starlette==1.6.0
50
+ sniffio==1.3.1
51
+ tenacity==9.1.4
52
+ threadpoolctl==3.6.0
53
+ typing-inspection==0.4.4
54
+ typing_extensions==4.16.0
55
+ tzdata==2026.3
56
+ urllib3==2.7.0
57
+ uuid_utils==0.17.0
58
+ uvicorn==0.52.4
59
+ watchfiles==1.2.0
60
+ websockets==16.1.1
61
+ xxhash==4.0.1
62
+ zstandard==0.25.0
scoring/.gitkeep ADDED
@@ -0,0 +1 @@
 
 
1
+
scoring/features.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ features.py
3
+ -----------
4
+ Feature extraction for RingGuard risk model.
5
+
6
+ Given a candidate cluster (from candidate_clusters.json) plus the matching
7
+ orders.json, customers.json, and optionally graph data, produces exactly 13
8
+ features per cluster as specified in the Phase 2 plan.
9
+
10
+ Feature set:
11
+ size, strong_link_count, weak_link_count, strong_link_density,
12
+ has_same_contact_edge, order_count, total_amount, avg_order_amount,
13
+ return_rate, chargeback_rate, shared_coupon_count, time_span_days,
14
+ order_velocity, formation_type_strong
15
+
16
+ Usage:
17
+ from scoring.features import extract_features, label
18
+
19
+ features = extract_features(cluster, orders, graph=G)
20
+ lbl = label(cluster, ground_truth)
21
+ """
22
+
23
+ import json
24
+ import os
25
+ import pickle
26
+ from datetime import datetime
27
+ from typing import Optional
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Helpers
32
+ # ---------------------------------------------------------------------------
33
+
34
+ def _load_json(path: str):
35
+ with open(path, encoding="utf-8") as f:
36
+ return json.load(f)
37
+
38
+
39
+ def _load_graph(path: str):
40
+ try:
41
+ import networkx as nx
42
+ return nx.read_gpickle(path)
43
+ except AttributeError:
44
+ with open(path, "rb") as f:
45
+ return pickle.load(f)
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Feature extraction
50
+ # ---------------------------------------------------------------------------
51
+
52
+ def extract_features(
53
+ cluster: dict,
54
+ orders: list[dict],
55
+ graph=None,
56
+ ) -> dict:
57
+ """
58
+ Extract exactly 13 features from a candidate cluster.
59
+
60
+ Parameters
61
+ ----------
62
+ cluster : dict
63
+ One entry from candidate_clusters.json. Must have:
64
+ customer_ids, size, strong_link_count, weak_link_count, formation_type
65
+ orders : list[dict]
66
+ All orders from the dataset (orders.json). Filtered to cluster members.
67
+ graph : networkx.Graph, optional
68
+ The heterogeneous graph (graph.gpickle). Used to detect same_contact edges.
69
+ If None, has_same_contact_edge is computed from cluster data only
70
+ (will be 0 unless the cluster was formed via same_contact; this is
71
+ a reasonable fallback since same_contact rings are always strong_anchored).
72
+
73
+ Returns
74
+ -------
75
+ dict with exactly 13 feature keys + cluster_id for identification.
76
+ """
77
+ member_set = set(cluster["customer_ids"])
78
+ size = cluster["size"]
79
+ strong_link_count = cluster["strong_link_count"]
80
+ weak_link_count = cluster["weak_link_count"]
81
+ formation_type = cluster.get("formation_type", "strong_anchored")
82
+
83
+ # Feature 1: size
84
+ # Feature 2: strong_link_count (from cluster record)
85
+ # Feature 3: weak_link_count (from cluster record)
86
+
87
+ # Feature 4: strong_link_density
88
+ max_possible = max(1, size * (size - 1) / 2)
89
+ strong_link_density = strong_link_count / max_possible
90
+
91
+ # Feature 5: has_same_contact_edge
92
+ # Check graph for same_contact edges between cluster members
93
+ has_same_contact_edge = 0
94
+ if graph is not None:
95
+ for u, v, data in graph.edges(data=True):
96
+ if data.get("relation") == "same_contact":
97
+ if u in member_set and v in member_set:
98
+ has_same_contact_edge = 1
99
+ break
100
+
101
+ # Filter orders to cluster members
102
+ cluster_orders = [o for o in orders if o["customer_id"] in member_set]
103
+
104
+ # Feature 6: order_count
105
+ order_count = len(cluster_orders)
106
+
107
+ # Feature 7: total_amount
108
+ total_amount = sum(o.get("amount", 0) for o in cluster_orders)
109
+
110
+ # Feature 8: avg_order_amount
111
+ avg_order_amount = total_amount / max(1, order_count)
112
+
113
+ # Feature 9: return_rate
114
+ returned = sum(1 for o in cluster_orders if o.get("status") == "returned")
115
+ return_rate = returned / max(1, order_count)
116
+
117
+ # Feature 10: chargeback_rate
118
+ chargebacks = sum(1 for o in cluster_orders if o.get("status") == "chargeback")
119
+ chargeback_rate = chargebacks / max(1, order_count)
120
+
121
+ # Feature 11: shared_coupon_count
122
+ # Count distinct coupon_id values used by 2+ different cluster members
123
+ coupon_to_members: dict[str, set] = {}
124
+ for o in cluster_orders:
125
+ cid_val = o.get("coupon_id")
126
+ if cid_val is not None:
127
+ if cid_val not in coupon_to_members:
128
+ coupon_to_members[cid_val] = set()
129
+ coupon_to_members[cid_val].add(o["customer_id"])
130
+ shared_coupon_count = sum(
131
+ 1 for members in coupon_to_members.values() if len(members) >= 2
132
+ )
133
+
134
+ # Features 12 + 13: time_span_days and order_velocity
135
+ timestamps = []
136
+ for o in cluster_orders:
137
+ ts_str = o.get("timestamp", "")
138
+ if ts_str:
139
+ try:
140
+ timestamps.append(datetime.fromisoformat(ts_str))
141
+ except ValueError:
142
+ pass
143
+
144
+ if len(timestamps) >= 2:
145
+ time_span_days = (max(timestamps) - min(timestamps)).total_seconds() / 86400.0
146
+ else:
147
+ time_span_days = 0.0
148
+
149
+ order_velocity = order_count / max(1.0, time_span_days)
150
+
151
+ # Feature 14 (index 13): formation_type_strong
152
+ formation_type_strong = 1 if formation_type == "strong_anchored" else 0
153
+
154
+ return {
155
+ "cluster_id": cluster["cluster_id"],
156
+ "size": size,
157
+ "strong_link_count": strong_link_count,
158
+ "weak_link_count": weak_link_count,
159
+ "strong_link_density": strong_link_density,
160
+ "has_same_contact_edge": has_same_contact_edge,
161
+ "order_count": order_count,
162
+ "total_amount": total_amount,
163
+ "avg_order_amount": avg_order_amount,
164
+ "return_rate": return_rate,
165
+ "chargeback_rate": chargeback_rate,
166
+ "shared_coupon_count": shared_coupon_count,
167
+ "time_span_days": time_span_days,
168
+ "order_velocity": order_velocity,
169
+ "formation_type_strong": formation_type_strong,
170
+ }
171
+
172
+
173
+ # ---------------------------------------------------------------------------
174
+ # Labeling
175
+ # ---------------------------------------------------------------------------
176
+
177
+ FEATURE_COLUMNS = [
178
+ "size",
179
+ "strong_link_count",
180
+ "weak_link_count",
181
+ "strong_link_density",
182
+ "has_same_contact_edge",
183
+ "order_count",
184
+ "total_amount",
185
+ "avg_order_amount",
186
+ "return_rate",
187
+ "chargeback_rate",
188
+ "shared_coupon_count",
189
+ "time_span_days",
190
+ "order_velocity",
191
+ "formation_type_strong",
192
+ ]
193
+
194
+
195
+ def label(cluster: dict, ground_truth: dict) -> int:
196
+ """
197
+ Returns 1 if >= 50% of the cluster's customer_ids overlap with any
198
+ ground-truth ring, else 0.
199
+
200
+ Parameters
201
+ ----------
202
+ cluster : dict
203
+ One entry from candidate_clusters.json.
204
+ ground_truth : dict
205
+ Loaded ground_truth.json. Must have a "rings" key.
206
+ """
207
+ cluster_members = set(cluster["customer_ids"])
208
+ threshold = len(cluster_members) * 0.5
209
+
210
+ for ring in ground_truth.get("rings", []):
211
+ ring_members = set(ring["customer_ids"])
212
+ overlap = len(cluster_members & ring_members)
213
+ if overlap >= threshold:
214
+ return 1
215
+
216
+ return 0
217
+
218
+
219
+ # ---------------------------------------------------------------------------
220
+ # Batch extraction
221
+ # ---------------------------------------------------------------------------
222
+
223
+ def extract_all(
224
+ clusters: list[dict],
225
+ orders: list[dict],
226
+ ground_truth: dict = None,
227
+ graph=None,
228
+ ) -> list[dict]:
229
+ """
230
+ Extract features for every cluster in the list.
231
+ If ground_truth is provided, adds a 'label' field to each row.
232
+
233
+ Returns list of feature dicts (one per cluster), ready for pandas or json.
234
+ """
235
+ rows = []
236
+ for cluster in clusters:
237
+ row = extract_features(cluster, orders, graph=graph)
238
+ if ground_truth is not None:
239
+ row["label"] = label(cluster, ground_truth)
240
+ rows.append(row)
241
+ return rows
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # CLI convenience: run directly to verify feature extraction on a dataset
246
+ # ---------------------------------------------------------------------------
247
+
248
+ if __name__ == "__main__":
249
+ import argparse
250
+ import sys
251
+
252
+ parser = argparse.ArgumentParser()
253
+ parser.add_argument("--data-dir", default=None,
254
+ help="Path to data directory. Defaults to data/synthetic_train/")
255
+ args = parser.parse_args()
256
+
257
+ base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
258
+ data_dir = args.data_dir or os.path.join(base, "data", "synthetic_train")
259
+
260
+ clusters_path = os.path.join(data_dir, "candidate_clusters.json")
261
+ orders_path = os.path.join(data_dir, "orders.json")
262
+ gt_path = os.path.join(data_dir, "ground_truth.json")
263
+ graph_path = os.path.join(data_dir, "graph.gpickle")
264
+
265
+ clusters = _load_json(clusters_path)
266
+ orders = _load_json(orders_path)
267
+ ground_truth = _load_json(gt_path)
268
+ graph = _load_graph(graph_path) if os.path.exists(graph_path) else None
269
+
270
+ rows = extract_all(clusters, orders, ground_truth=ground_truth, graph=graph)
271
+
272
+ # Sanity checks
273
+ assert all(len(r) == len(FEATURE_COLUMNS) + 2 for r in rows), \
274
+ "Row has wrong number of features (expected 14 features + cluster_id + label)"
275
+
276
+ null_count = sum(
277
+ 1 for r in rows for k in FEATURE_COLUMNS if r.get(k) is None
278
+ )
279
+ assert null_count == 0, f"Null values found in feature table: {null_count}"
280
+
281
+ labels = [r["label"] for r in rows]
282
+ pos = sum(labels)
283
+ neg = len(labels) - pos
284
+
285
+ print(f"Feature extraction OK: {len(rows)} clusters")
286
+ print(f" Positive labels (fraud ring): {pos}")
287
+ print(f" Negative labels (benign): {neg}")
288
+ print(f" Null values in features: {null_count}")
289
+ print("\nSample row:")
290
+ sample = rows[0]
291
+ for k in FEATURE_COLUMNS:
292
+ print(f" {k:30s}: {sample[k]:.4f}" if isinstance(sample[k], float)
293
+ else f" {k:30s}: {sample[k]}")
scoring/holdout_split.json ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "cluster_id": "CAND45",
4
+ "features": {
5
+ "size": 8,
6
+ "strong_link_count": 28,
7
+ "weak_link_count": 0,
8
+ "strong_link_density": 1.0,
9
+ "has_same_contact_edge": 0,
10
+ "order_count": 21,
11
+ "total_amount": 99226,
12
+ "avg_order_amount": 4725.047619047619,
13
+ "return_rate": 0.38095238095238093,
14
+ "chargeback_rate": 0.14285714285714285,
15
+ "shared_coupon_count": 2,
16
+ "time_span_days": 160.50546296296295,
17
+ "order_velocity": 0.13083666818771025,
18
+ "formation_type_strong": 1
19
+ },
20
+ "label": 1,
21
+ "risk_probability": 0.996939116034507
22
+ },
23
+ {
24
+ "cluster_id": "CAND59",
25
+ "features": {
26
+ "size": 4,
27
+ "strong_link_count": 6,
28
+ "weak_link_count": 0,
29
+ "strong_link_density": 1.0,
30
+ "has_same_contact_edge": 0,
31
+ "order_count": 11,
32
+ "total_amount": 42505,
33
+ "avg_order_amount": 3864.090909090909,
34
+ "return_rate": 0.45454545454545453,
35
+ "chargeback_rate": 0.18181818181818182,
36
+ "shared_coupon_count": 0,
37
+ "time_span_days": 148.44283564814816,
38
+ "order_velocity": 0.07410259950889873,
39
+ "formation_type_strong": 1
40
+ },
41
+ "label": 1,
42
+ "risk_probability": 0.9967870481969796
43
+ },
44
+ {
45
+ "cluster_id": "CAND97",
46
+ "features": {
47
+ "size": 3,
48
+ "strong_link_count": 0,
49
+ "weak_link_count": 3,
50
+ "strong_link_density": 0.0,
51
+ "has_same_contact_edge": 0,
52
+ "order_count": 9,
53
+ "total_amount": 23023,
54
+ "avg_order_amount": 2558.1111111111113,
55
+ "return_rate": 0.3333333333333333,
56
+ "chargeback_rate": 0.0,
57
+ "shared_coupon_count": 0,
58
+ "time_span_days": 154.36827546296297,
59
+ "order_velocity": 0.0583021347683536,
60
+ "formation_type_strong": 0
61
+ },
62
+ "label": 0,
63
+ "risk_probability": 0.0035484719416527992
64
+ },
65
+ {
66
+ "cluster_id": "CAND54",
67
+ "features": {
68
+ "size": 5,
69
+ "strong_link_count": 6,
70
+ "weak_link_count": 0,
71
+ "strong_link_density": 0.6,
72
+ "has_same_contact_edge": 1,
73
+ "order_count": 18,
74
+ "total_amount": 64576,
75
+ "avg_order_amount": 3587.5555555555557,
76
+ "return_rate": 0.5555555555555556,
77
+ "chargeback_rate": 0.1111111111111111,
78
+ "shared_coupon_count": 0,
79
+ "time_span_days": 168.73714120370371,
80
+ "order_velocity": 0.10667479531533575,
81
+ "formation_type_strong": 1
82
+ },
83
+ "label": 1,
84
+ "risk_probability": 0.9952350778861326
85
+ },
86
+ {
87
+ "cluster_id": "CAND48",
88
+ "features": {
89
+ "size": 6,
90
+ "strong_link_count": 9,
91
+ "weak_link_count": 0,
92
+ "strong_link_density": 0.6,
93
+ "has_same_contact_edge": 1,
94
+ "order_count": 17,
95
+ "total_amount": 64348,
96
+ "avg_order_amount": 3785.176470588235,
97
+ "return_rate": 0.35294117647058826,
98
+ "chargeback_rate": 0.11764705882352941,
99
+ "shared_coupon_count": 1,
100
+ "time_span_days": 153.33387731481483,
101
+ "order_velocity": 0.11086917188624103,
102
+ "formation_type_strong": 1
103
+ },
104
+ "label": 1,
105
+ "risk_probability": 0.9967341961721978
106
+ },
107
+ {
108
+ "cluster_id": "CAND66",
109
+ "features": {
110
+ "size": 109,
111
+ "strong_link_count": 0,
112
+ "weak_link_count": 246,
113
+ "strong_link_density": 0.0,
114
+ "has_same_contact_edge": 0,
115
+ "order_count": 287,
116
+ "total_amount": 804955,
117
+ "avg_order_amount": 2804.7212543554006,
118
+ "return_rate": 0.1986062717770035,
119
+ "chargeback_rate": 0.04878048780487805,
120
+ "shared_coupon_count": 0,
121
+ "time_span_days": 177.63575231481482,
122
+ "order_velocity": 1.6156657444238167,
123
+ "formation_type_strong": 0
124
+ },
125
+ "label": 0,
126
+ "risk_probability": 0.0035456513474297945
127
+ },
128
+ {
129
+ "cluster_id": "CAND108",
130
+ "features": {
131
+ "size": 3,
132
+ "strong_link_count": 0,
133
+ "weak_link_count": 3,
134
+ "strong_link_density": 0.0,
135
+ "has_same_contact_edge": 0,
136
+ "order_count": 5,
137
+ "total_amount": 11646,
138
+ "avg_order_amount": 2329.2,
139
+ "return_rate": 0.4,
140
+ "chargeback_rate": 0.0,
141
+ "shared_coupon_count": 0,
142
+ "time_span_days": 107.73620370370371,
143
+ "order_velocity": 0.04640965458325419,
144
+ "formation_type_strong": 0
145
+ },
146
+ "label": 0,
147
+ "risk_probability": 0.0029273850745234796
148
+ },
149
+ {
150
+ "cluster_id": "CAND87",
151
+ "features": {
152
+ "size": 83,
153
+ "strong_link_count": 0,
154
+ "weak_link_count": 222,
155
+ "strong_link_density": 0.0,
156
+ "has_same_contact_edge": 0,
157
+ "order_count": 209,
158
+ "total_amount": 582089,
159
+ "avg_order_amount": 2785.114832535885,
160
+ "return_rate": 0.1674641148325359,
161
+ "chargeback_rate": 0.05741626794258373,
162
+ "shared_coupon_count": 0,
163
+ "time_span_days": 179.47063657407406,
164
+ "order_velocity": 1.164535903976348,
165
+ "formation_type_strong": 0
166
+ },
167
+ "label": 0,
168
+ "risk_probability": 0.0035456513474297945
169
+ },
170
+ {
171
+ "cluster_id": "CAND02",
172
+ "features": {
173
+ "size": 6,
174
+ "strong_link_count": 15,
175
+ "weak_link_count": 0,
176
+ "strong_link_density": 1.0,
177
+ "has_same_contact_edge": 0,
178
+ "order_count": 18,
179
+ "total_amount": 61849,
180
+ "avg_order_amount": 3436.0555555555557,
181
+ "return_rate": 0.3333333333333333,
182
+ "chargeback_rate": 0.16666666666666666,
183
+ "shared_coupon_count": 0,
184
+ "time_span_days": 172.4928125,
185
+ "order_velocity": 0.10435217409421044,
186
+ "formation_type_strong": 1
187
+ },
188
+ "label": 1,
189
+ "risk_probability": 0.9920524617512916
190
+ },
191
+ {
192
+ "cluster_id": "CAND09",
193
+ "features": {
194
+ "size": 6,
195
+ "strong_link_count": 11,
196
+ "weak_link_count": 0,
197
+ "strong_link_density": 0.7333333333333333,
198
+ "has_same_contact_edge": 1,
199
+ "order_count": 19,
200
+ "total_amount": 73548,
201
+ "avg_order_amount": 3870.9473684210525,
202
+ "return_rate": 0.3157894736842105,
203
+ "chargeback_rate": 0.10526315789473684,
204
+ "shared_coupon_count": 1,
205
+ "time_span_days": 168.76631944444443,
206
+ "order_velocity": 0.11258170506144469,
207
+ "formation_type_strong": 1
208
+ },
209
+ "label": 1,
210
+ "risk_probability": 0.9969428584522867
211
+ },
212
+ {
213
+ "cluster_id": "CAND52",
214
+ "features": {
215
+ "size": 6,
216
+ "strong_link_count": 15,
217
+ "weak_link_count": 0,
218
+ "strong_link_density": 1.0,
219
+ "has_same_contact_edge": 0,
220
+ "order_count": 18,
221
+ "total_amount": 65370,
222
+ "avg_order_amount": 3631.6666666666665,
223
+ "return_rate": 0.3888888888888889,
224
+ "chargeback_rate": 0.1111111111111111,
225
+ "shared_coupon_count": 1,
226
+ "time_span_days": 120.77247685185185,
227
+ "order_velocity": 0.14904058001625722,
228
+ "formation_type_strong": 1
229
+ },
230
+ "label": 1,
231
+ "risk_probability": 0.9958819841283693
232
+ },
233
+ {
234
+ "cluster_id": "CAND07",
235
+ "features": {
236
+ "size": 5,
237
+ "strong_link_count": 7,
238
+ "weak_link_count": 0,
239
+ "strong_link_density": 0.7,
240
+ "has_same_contact_edge": 1,
241
+ "order_count": 14,
242
+ "total_amount": 52492,
243
+ "avg_order_amount": 3749.4285714285716,
244
+ "return_rate": 0.2857142857142857,
245
+ "chargeback_rate": 0.2857142857142857,
246
+ "shared_coupon_count": 0,
247
+ "time_span_days": 132.76825231481482,
248
+ "order_velocity": 0.10544689529243599,
249
+ "formation_type_strong": 1
250
+ },
251
+ "label": 1,
252
+ "risk_probability": 0.9959009223567944
253
+ },
254
+ {
255
+ "cluster_id": "CAND73",
256
+ "features": {
257
+ "size": 68,
258
+ "strong_link_count": 1,
259
+ "weak_link_count": 183,
260
+ "strong_link_density": 0.0004389815627743635,
261
+ "has_same_contact_edge": 0,
262
+ "order_count": 187,
263
+ "total_amount": 458341,
264
+ "avg_order_amount": 2451.0213903743315,
265
+ "return_rate": 0.1443850267379679,
266
+ "chargeback_rate": 0.09090909090909091,
267
+ "shared_coupon_count": 1,
268
+ "time_span_days": 180.15159722222222,
269
+ "order_velocity": 1.0380146658890295,
270
+ "formation_type_strong": 0
271
+ },
272
+ "label": 0,
273
+ "risk_probability": 0.007239076424852942
274
+ },
275
+ {
276
+ "cluster_id": "CAND29",
277
+ "features": {
278
+ "size": 4,
279
+ "strong_link_count": 6,
280
+ "weak_link_count": 0,
281
+ "strong_link_density": 1.0,
282
+ "has_same_contact_edge": 1,
283
+ "order_count": 9,
284
+ "total_amount": 52516,
285
+ "avg_order_amount": 5835.111111111111,
286
+ "return_rate": 0.2222222222222222,
287
+ "chargeback_rate": 0.3333333333333333,
288
+ "shared_coupon_count": 1,
289
+ "time_span_days": 163.2079513888889,
290
+ "order_velocity": 0.05514437209345865,
291
+ "formation_type_strong": 1
292
+ },
293
+ "label": 1,
294
+ "risk_probability": 0.9968779996975877
295
+ },
296
+ {
297
+ "cluster_id": "CAND79",
298
+ "features": {
299
+ "size": 64,
300
+ "strong_link_count": 0,
301
+ "weak_link_count": 153,
302
+ "strong_link_density": 0.0,
303
+ "has_same_contact_edge": 0,
304
+ "order_count": 174,
305
+ "total_amount": 460737,
306
+ "avg_order_amount": 2647.9137931034484,
307
+ "return_rate": 0.21264367816091953,
308
+ "chargeback_rate": 0.040229885057471264,
309
+ "shared_coupon_count": 0,
310
+ "time_span_days": 178.9133101851852,
311
+ "order_velocity": 0.9725380399026788,
312
+ "formation_type_strong": 0
313
+ },
314
+ "label": 0,
315
+ "risk_probability": 0.0035456513474297945
316
+ },
317
+ {
318
+ "cluster_id": "CAND69",
319
+ "features": {
320
+ "size": 69,
321
+ "strong_link_count": 1,
322
+ "weak_link_count": 163,
323
+ "strong_link_density": 0.00042625745950554135,
324
+ "has_same_contact_edge": 0,
325
+ "order_count": 162,
326
+ "total_amount": 463506,
327
+ "avg_order_amount": 2861.1481481481483,
328
+ "return_rate": 0.2222222222222222,
329
+ "chargeback_rate": 0.043209876543209874,
330
+ "shared_coupon_count": 1,
331
+ "time_span_days": 174.4325810185185,
332
+ "order_velocity": 0.9287255801300182,
333
+ "formation_type_strong": 0
334
+ },
335
+ "label": 0,
336
+ "risk_probability": 0.0033839575968115728
337
+ },
338
+ {
339
+ "cluster_id": "CAND77",
340
+ "features": {
341
+ "size": 112,
342
+ "strong_link_count": 0,
343
+ "weak_link_count": 265,
344
+ "strong_link_density": 0.0,
345
+ "has_same_contact_edge": 0,
346
+ "order_count": 290,
347
+ "total_amount": 774776,
348
+ "avg_order_amount": 2671.6413793103447,
349
+ "return_rate": 0.18275862068965518,
350
+ "chargeback_rate": 0.06206896551724138,
351
+ "shared_coupon_count": 0,
352
+ "time_span_days": 180.6612037037037,
353
+ "order_velocity": 1.605214589822058,
354
+ "formation_type_strong": 0
355
+ },
356
+ "label": 0,
357
+ "risk_probability": 0.0035456513474297945
358
+ },
359
+ {
360
+ "cluster_id": "CAND81",
361
+ "features": {
362
+ "size": 71,
363
+ "strong_link_count": 1,
364
+ "weak_link_count": 160,
365
+ "strong_link_density": 0.00040241448692152917,
366
+ "has_same_contact_edge": 1,
367
+ "order_count": 204,
368
+ "total_amount": 568308,
369
+ "avg_order_amount": 2785.823529411765,
370
+ "return_rate": 0.19117647058823528,
371
+ "chargeback_rate": 0.05392156862745098,
372
+ "shared_coupon_count": 1,
373
+ "time_span_days": 178.23292824074073,
374
+ "order_velocity": 1.144569648344976,
375
+ "formation_type_strong": 0
376
+ },
377
+ "label": 0,
378
+ "risk_probability": 0.0035711032326407374
379
+ },
380
+ {
381
+ "cluster_id": "CAND101",
382
+ "features": {
383
+ "size": 3,
384
+ "strong_link_count": 0,
385
+ "weak_link_count": 3,
386
+ "strong_link_density": 0.0,
387
+ "has_same_contact_edge": 0,
388
+ "order_count": 5,
389
+ "total_amount": 6796,
390
+ "avg_order_amount": 1359.2,
391
+ "return_rate": 0.6,
392
+ "chargeback_rate": 0.0,
393
+ "shared_coupon_count": 0,
394
+ "time_span_days": 138.31976851851852,
395
+ "order_velocity": 0.03614812295850965,
396
+ "formation_type_strong": 0
397
+ },
398
+ "label": 0,
399
+ "risk_probability": 0.002762985350619287
400
+ },
401
+ {
402
+ "cluster_id": "CAND08",
403
+ "features": {
404
+ "size": 6,
405
+ "strong_link_count": 15,
406
+ "weak_link_count": 0,
407
+ "strong_link_density": 1.0,
408
+ "has_same_contact_edge": 0,
409
+ "order_count": 18,
410
+ "total_amount": 76645,
411
+ "avg_order_amount": 4258.055555555556,
412
+ "return_rate": 0.3888888888888889,
413
+ "chargeback_rate": 0.05555555555555555,
414
+ "shared_coupon_count": 1,
415
+ "time_span_days": 167.69203703703704,
416
+ "order_velocity": 0.10733962278736263,
417
+ "formation_type_strong": 1
418
+ },
419
+ "label": 1,
420
+ "risk_probability": 0.9940880005500065
421
+ },
422
+ {
423
+ "cluster_id": "CAND27",
424
+ "features": {
425
+ "size": 5,
426
+ "strong_link_count": 10,
427
+ "weak_link_count": 0,
428
+ "strong_link_density": 1.0,
429
+ "has_same_contact_edge": 0,
430
+ "order_count": 17,
431
+ "total_amount": 61207,
432
+ "avg_order_amount": 3600.4117647058824,
433
+ "return_rate": 0.17647058823529413,
434
+ "chargeback_rate": 0.17647058823529413,
435
+ "shared_coupon_count": 1,
436
+ "time_span_days": 156.60148148148147,
437
+ "order_velocity": 0.1085558057253136,
438
+ "formation_type_strong": 1
439
+ },
440
+ "label": 1,
441
+ "risk_probability": 0.996945496633462
442
+ },
443
+ {
444
+ "cluster_id": "CAND20",
445
+ "features": {
446
+ "size": 4,
447
+ "strong_link_count": 6,
448
+ "weak_link_count": 0,
449
+ "strong_link_density": 1.0,
450
+ "has_same_contact_edge": 0,
451
+ "order_count": 15,
452
+ "total_amount": 57239,
453
+ "avg_order_amount": 3815.9333333333334,
454
+ "return_rate": 0.3333333333333333,
455
+ "chargeback_rate": 0.13333333333333333,
456
+ "shared_coupon_count": 1,
457
+ "time_span_days": 160.63930555555555,
458
+ "order_velocity": 0.09337689769091037,
459
+ "formation_type_strong": 1
460
+ },
461
+ "label": 1,
462
+ "risk_probability": 0.9969378410557287
463
+ }
464
+ ]
scoring/model.lgb ADDED
@@ -0,0 +1,2054 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ tree
2
+ version=v4
3
+ num_class=1
4
+ num_tree_per_iteration=1
5
+ label_index=0
6
+ max_feature_idx=13
7
+ objective=binary sigmoid:1
8
+ feature_names=Column_0 Column_1 Column_2 Column_3 Column_4 Column_5 Column_6 Column_7 Column_8 Column_9 Column_10 Column_11 Column_12 Column_13
9
+ feature_infos=[3:168] [0:28] [0:452] [0:1] [0:1] [5:439] [10305:1291235] [2061:4548.8000000000002] [0:0.5714285714285714] [0:0.33333333333333331] [0:2] [80.053703703703704:180.73202546296295] [0.028612871818308266:2.4496926129718695] [0:1]
10
+ tree_sizes=553 874 769 877 874 881 880 873 873 880 882 876 878 880 885 878 887 871 875 880 881 877 883 879 879 877 884 883 877 881 880 879 883 887 886 881 887 885 883 886 884 886 886 889 888 887 890 881 887 892 885 893 884 890 893 887 888 886 891 890 883 890 886 890 891 886 886 887 891 890 888 895 890 894 892 893 896 895 893 895 895 894 895 894 896 897 894 895 893 890 897 899 892 895 895 898 887 895 896 896
11
+
12
+ Tree=0
13
+ num_leaves=4
14
+ num_cat=0
15
+ split_feature=2 11 5
16
+ split_gain=82.066 0.71839 7.10543e-15
17
+ threshold=1.0000000180025095e-35 133.51370949074075 6.5000000000000009
18
+ decision_type=2 2 2
19
+ left_child=1 -1 -2
20
+ right_child=2 -3 -4
21
+ leaf_value=0.1930445524210789 0.032261939230608494 0.23324020571869669 0.032261939230608494
22
+ leaf_weight=1.2439156323671365 1.4926987588405598 10.448891311883925 8.2098431736230832
23
+ leaf_count=5 6 42 33
24
+ internal_value=0.139762 0.228964 0.0322619
25
+ internal_weight=21.3953 11.6928 9.70254
26
+ internal_count=86 47 39
27
+ is_linear=0
28
+ shrinkage=1
29
+
30
+
31
+ Tree=1
32
+ num_leaves=7
33
+ num_cat=0
34
+ split_feature=7 1 5 11 0 4
35
+ split_gain=74.4604 0.665595 0.00139175 0.000597925 7.10543e-15 7.10543e-15
36
+ threshold=3112.1875000000005 2.5000000000000004 5.5000000000000009 141.79473379629633 7.0000000000000009 1.0000000180025095e-35
37
+ decision_type=2 2 2 2 2 2
38
+ left_child=2 -2 -1 -3 -4 -5
39
+ right_child=1 3 4 5 -6 -7
40
+ leaf_value=-0.10342789578905492 0.050941784258864947 0.090682433465454779 -0.10163940525078299 0.089598162538490678 -0.10163940525078292 0.089598162538490636
41
+ leaf_weight=1.2474250793457056 1.2364570945501316 1.4840020835399617 2.4993495643138868 5.6725022941827756 5.9984389543533325 3.2061969488859177
42
+ leaf_count=5 5 6 10 23 24 13
43
+ internal_value=1.62416e-05 0.0856162 -0.101868 0.0897534 -0.101639 0.0895982
44
+ internal_weight=21.3444 11.5992 9.74521 10.3627 8.49779 8.8787
45
+ internal_count=86 47 39 42 34 36
46
+ is_linear=0
47
+ shrinkage=0.05
48
+
49
+
50
+ Tree=2
51
+ num_leaves=6
52
+ num_cat=0
53
+ split_feature=7 1 12 11 6
54
+ split_gain=67.6008 0.62316 0.00108445 0.000455319 4.73442e-07
55
+ threshold=3112.1875000000005 2.5000000000000004 0.046585109941867327 141.79473379629633 17835.500000000004
56
+ decision_type=2 2 2 2 2
57
+ left_child=2 -2 -1 -3 -4
58
+ right_child=1 3 4 -5 -6
59
+ leaf_value=-0.098222188977615796 0.048669522557685856 0.087156175904335012 -0.096615383620140993 0.086204543567968514 -0.0966487227699587
60
+ leaf_weight=1.248264998197558 1.2296466380357731 1.467251956462859 1.2484656870365123 8.7695086598396284 7.2412829995155334
61
+ leaf_count=5 5 6 5 36 29
62
+ internal_value=2.87115e-05 0.0823011 -0.0968461 0.0863409 -0.0966438
63
+ internal_weight=21.2044 11.4664 9.73801 10.2368 8.48975
64
+ internal_count=86 47 39 42 34
65
+ is_linear=0
66
+ shrinkage=0.05
67
+
68
+
69
+ Tree=3
70
+ num_leaves=7
71
+ num_cat=0
72
+ split_feature=2 11 8 12 6 7
73
+ split_gain=61.8731 0.587705 0.00367442 0.00110176 1.22284e-06 2.40187e-07
74
+ threshold=1.0000000180025095e-35 133.51370949074075 0.37980769230769235 0.065152412384244726 17835.500000000004 4317.0726495726512
75
+ decision_type=2 2 2 2 2 2
76
+ left_child=1 -1 4 -3 -2 -5
77
+ right_child=2 3 -4 5 -6 -7
78
+ leaf_value=0.04667381315066154 -0.092295175394442819 0.084538242823278584 -0.095253852398532762 0.083214287911124257 -0.0923489100898221 0.083191776414990704
79
+ leaf_weight=1.2163800746202493 1.2412882149219493 1.9321163296699513 1.2419511824846257 6.7151724696159345 7.2002349644899368 1.4387681186199186
80
+ leaf_count=5 5 8 5 28 29 6
81
+ internal_value=3.82822e-05 0.0795052 -0.0927146 0.0834647 -0.092341 0.0832103
82
+ internal_weight=20.9859 11.3024 9.68347 10.0861 8.44152 8.15394
83
+ internal_count=86 47 39 42 34 34
84
+ is_linear=0
85
+ shrinkage=0.05
86
+
87
+
88
+ Tree=4
89
+ num_leaves=7
90
+ num_cat=0
91
+ split_feature=7 1 12 11 11 0
92
+ split_gain=56.6084 0.560845 0.00294361 0.00121052 2.37162e-06 4.26503e-07
93
+ threshold=3112.1875000000005 2.5000000000000004 0.046585109941867327 141.79473379629633 133.51370949074075 3.5000000000000004
94
+ decision_type=2 2 2 2 2 2
95
+ left_child=2 -2 -1 -3 -4 -5
96
+ right_child=1 3 4 5 -6 -7
97
+ leaf_value=-0.091218722018037074 0.044692577885030123 0.082131324226660612 -0.088549404265767295 0.080529911728688675 -0.088615273661690988 0.080562347051243738
98
+ leaf_weight=1.2330005764961267 1.2075967341661442 1.4283113479614247 1.7207350879907588 1.176931157708166 6.638588011264801 7.2988456338644028
99
+ leaf_count=5 5 6 7 5 27 31
100
+ internal_value=4.57358e-05 0.0768623 -0.0889381 0.0807848 -0.0886017 0.0805578
101
+ internal_weight=20.704 11.1117 9.59232 9.90409 8.35932 8.47578
102
+ internal_count=86 47 39 42 34 36
103
+ is_linear=0
104
+ shrinkage=0.05
105
+
106
+
107
+ Tree=5
108
+ num_leaves=7
109
+ num_cat=0
110
+ split_feature=2 11 8 12 12 11
111
+ split_gain=52.1697 0.534151 0.00597517 0.00166896 3.92204e-06 5.09929e-07
112
+ threshold=1.0000000180025095e-35 133.51370949074075 0.37980769230769235 0.065152412384244726 0.057153336188971379 148.79337384259262
113
+ decision_type=2 2 2 2 2 2
114
+ left_child=1 -1 4 -3 -2 -5
115
+ right_child=2 3 -4 5 -6 -7
116
+ leaf_value=0.043017542984362105 -0.08524151794941176 0.079853079758136811 -0.089074561699500135 0.078163284367070693 -0.085338883602630033 0.078196740304427539
117
+ leaf_weight=1.1899101287126566 1.2125268876552562 1.8703867793083171 1.2192777097225178 1.3829243779182432 7.0360173434019089 6.4557932019233704
118
+ leaf_count=5 5 8 5 6 29 28
119
+ internal_value=5.15016e-05 0.074636 -0.0858075 0.0785111 -0.0853246 0.0781908
120
+ internal_weight=20.3668 10.899 9.46782 9.7091 8.24854 7.83872
121
+ internal_count=86 47 39 42 34 34
122
+ is_linear=0
123
+ shrinkage=0.05
124
+
125
+
126
+ Tree=6
127
+ num_leaves=7
128
+ num_cat=0
129
+ split_feature=2 11 8 12 12 7
130
+ split_gain=48.0088 0.51813 0.00474055 0.00131991 3.02662e-06 4.05399e-07
131
+ threshold=1.0000000180025095e-35 133.51370949074075 0.37980769230769235 0.065152412384244726 0.057153336188971379 4317.0726495726512
132
+ decision_type=2 2 2 2 2 2
133
+ left_child=1 -1 4 -3 -2 -5
134
+ right_child=2 3 -4 5 -6 -7
135
+ leaf_value=0.041224848892825315 -0.082361959085578224 0.077565104646246552 -0.085798940887919936 0.076075869805599397 -0.082448199492469315 0.076045690863144111
136
+ leaf_weight=1.179830446839335 1.192670613527296 1.8302927613258342 1.2029409408569325 6.3077281713485718 6.9214366972446442 1.3511618077754972
137
+ leaf_count=5 5 8 5 28 29 6
138
+ internal_value=5.60081e-05 0.0724735 -0.0828698 0.0763588 -0.0824355 0.0760705
139
+ internal_weight=19.9861 10.669 9.31705 9.48918 8.11411 7.65889
140
+ internal_count=86 47 39 42 34 34
141
+ is_linear=0
142
+ shrinkage=0.05
143
+
144
+
145
+ Tree=7
146
+ num_leaves=7
147
+ num_cat=0
148
+ split_feature=7 1 12 11 8 6
149
+ split_gain=44.4771 0.49671 0.00810296 0.0029023 1.09563e-05 2.78578e-06
150
+ threshold=3112.1875000000005 2.5000000000000004 0.046585109941867327 141.79473379629633 0.31009615384615391 42986.000000000007
151
+ decision_type=2 2 2 2 2 2
152
+ left_child=2 -2 -1 -3 -4 -5
153
+ right_child=1 3 4 5 -6 -7
154
+ leaf_value=-0.084285547508256564 0.039805551243280855 0.076655996510183039 -0.079880561303504738 0.074080055285562552 -0.079726609628141556 0.074165912972976755
155
+ leaf_weight=1.1847488284111038 1.1593277156352986 1.3581690192222575 6.5559337288141251 1.096968740224838 1.4029920995235441 6.8096721768379211
156
+ leaf_count=5 5 6 28 5 6 31
157
+ internal_value=5.94785e-05 0.0706599 -0.0804277 0.0745208 -0.0798534 0.074154
158
+ internal_weight=19.5678 10.4241 9.14367 9.26481 7.95893 7.90664
159
+ internal_count=86 47 39 42 34 36
160
+ is_linear=0
161
+ shrinkage=0.05
162
+
163
+
164
+ Tree=8
165
+ num_leaves=7
166
+ num_cat=0
167
+ split_feature=7 8 5 6 4 9
168
+ split_gain=41.1179 0.495951 0.00664183 0.00222736 0.00167781 0.000410439
169
+ threshold=3112.1875000000005 0.40833333333333338 5.5000000000000009 42986.000000000007 1.0000000180025095e-35 0.21825396825396828
170
+ decision_type=2 2 2 2 2 2
171
+ left_child=2 3 -1 4 -2 -5
172
+ right_child=1 -3 -4 5 -6 -7
173
+ leaf_value=-0.081609467453499146 0.073086372699730492 0.040246179662398615 -0.077559324356391984 0.072426785842704122 0.075847270191939598 0.073321822227397235
174
+ leaf_weight=1.1634619385004059 1.0787670463323575 1.3185045868158329 7.7883933484554291 4.9166114330291748 1.1232455372810364 1.7321313172578809
175
+ leaf_count=5 5 6 34 23 5 8
176
+ internal_value=6.21514e-05 0.0688546 -0.0780857 0.0731164 0.0744947 0.07266
177
+ internal_weight=19.1211 10.1693 8.95186 8.85076 2.20201 6.64874
178
+ internal_count=86 47 39 41 10 31
179
+ is_linear=0
180
+ shrinkage=0.05
181
+
182
+
183
+ Tree=9
184
+ num_leaves=7
185
+ num_cat=0
186
+ split_feature=2 11 8 12 8 12
187
+ split_gain=38.2564 0.47099 0.00971658 0.00134374 9.15222e-05 8.5431e-06
188
+ threshold=1.0000000180025095e-35 133.51370949074075 0.37980769230769235 0.065152412384244726 0.37980769230769235 0.057153336188971379
189
+ decision_type=2 2 2 2 2 2
190
+ left_child=1 -1 5 -3 -5 -2
191
+ right_child=2 3 -4 4 -6 -7
192
+ leaf_value=0.036884259324746746 -0.075374644139064667 0.072503460712974296 -0.080451132838729761 0.070857408177662437 0.071327830184486976 -0.075524376303285998
193
+ leaf_weight=1.1250674128532434 1.1165634840726832 1.7085118144750575 1.1406608074903486 5.8159056305885315 1.2574799358844755 6.4885646849870682
194
+ leaf_count=5 5 8 5 28 6 29
195
+ internal_value=6.38395e-05 0.0673429 -0.0761478 0.071245 0.070941 -0.0755024
196
+ internal_weight=18.6528 9.90696 8.74579 8.7819 7.07339 7.60513
197
+ internal_count=86 47 39 42 34 34
198
+ is_linear=0
199
+ shrinkage=0.05
200
+
201
+
202
+ Tree=10
203
+ num_leaves=7
204
+ num_cat=0
205
+ split_feature=2 9 8 11 12 12
206
+ split_gain=35.4973 0.479056 0.00802536 0.00186363 0.00106585 6.79661e-06
207
+ threshold=1.0000000180025095e-35 0.075080247396852748 0.37980769230769235 141.79473379629633 0.065152412384244726 0.057153336188971379
208
+ decision_type=2 2 2 2 2 2
209
+ left_child=1 -1 5 -3 -5 -2
210
+ right_child=2 3 -4 4 -6 -7
211
+ leaf_value=0.037103223145715646 -0.073532368018833894 0.07190519761274379 -0.078196748553504059 0.070932617799112946 0.069485535107175603 -0.073667659169007235
212
+ leaf_weight=1.2629062384367014 1.0880334228277186 1.2667635977268199 1.115504205226898 1.660253584384918 5.448053240776062 6.3236725628376007
213
+ leaf_count=6 5 6 5 8 27 29
214
+ internal_value=6.53544e-05 0.0658096 -0.0742429 0.0701384 0.0698235 -0.0736478
215
+ internal_weight=18.1652 9.63798 8.52721 8.37507 7.10831 7.41171
216
+ internal_count=86 47 39 41 35 34
217
+ is_linear=0
218
+ shrinkage=0.05
219
+
220
+
221
+ Tree=11
222
+ num_leaves=7
223
+ num_cat=0
224
+ split_feature=7 1 12 11 9 11
225
+ split_gain=33.1373 0.475899 0.0114909 0.00185294 0.000220994 2.10641e-05
226
+ threshold=3112.1875000000005 2.5000000000000004 0.046585109941867327 141.79473379629633 0.075080247396852748 133.51370949074075
227
+ decision_type=2 2 2 2 2 2
228
+ left_child=2 -2 -1 -3 -5 -4
229
+ right_child=1 3 5 4 -6 -7
230
+ leaf_value=-0.077449289151936135 0.033279286118847784 0.070389415472064426 -0.071779567111123543 0.068976460844906745 0.06817315250376077 -0.07199119917480043
231
+ leaf_weight=1.0930734276771561 1.077754572033881 1.2302654534578303 1.4794269502162931 0.99700576066970814 6.0605845898389816 5.7282752245664597
232
+ leaf_count=5 5 6 7 5 31 27
233
+ internal_value=6.61067e-05 0.0645344 -0.0726722 0.0685988 0.0682866 -0.0719478
234
+ internal_weight=17.6664 9.36561 8.30078 8.28786 7.05759 7.2077
235
+ internal_count=86 47 39 42 36 34
236
+ is_linear=0
237
+ shrinkage=0.05
238
+
239
+
240
+ Tree=12
241
+ num_leaves=7
242
+ num_cat=0
243
+ split_feature=7 3 12 3 11 8
244
+ split_gain=30.8387 0.485179 0.00960963 0.0012348 0.000973892 1.71113e-05
245
+ threshold=3112.1875000000005 0.63333333333333341 0.046585109941867327 0.68333333333333346 141.79473379629633 0.31009615384615391
246
+ decision_type=2 2 2 2 2 2
247
+ left_child=2 -2 -1 -3 -5 -4
248
+ right_child=1 3 5 4 -6 -7
249
+ leaf_value=-0.075520827820082612 0.033501967349706951 0.068857893861478878 -0.070460258488882196 0.06879936231023942 0.067085578873881238 -0.070254919880213665
250
+ leaf_weight=1.0656314641237274 1.1938995271921138 1.7841720283031461 5.7696630209684372 0.98896507918834675 5.1240549236536026 1.2310415506362913
251
+ leaf_count=5 6 9 28 5 27 6
252
+ internal_value=6.68297e-05 0.0632094 -0.0710975 0.0677006 0.0673628 -0.0704242
253
+ internal_weight=17.1574 9.09109 8.06634 7.89719 6.11302 7.0007
254
+ internal_count=86 47 39 41 32 34
255
+ is_linear=0
256
+ shrinkage=0.05
257
+
258
+
259
+ Tree=13
260
+ num_leaves=7
261
+ num_cat=0
262
+ split_feature=2 11 8 12 9 12
263
+ split_gain=28.875 0.460053 0.012447 0.000856805 0.00011562 1.70654e-05
264
+ threshold=1.0000000180025095e-35 133.51370949074075 0.37980769230769235 0.065152412384244726 0.075080247396852748 0.057153336188971379
265
+ decision_type=2 2 2 2 2 2
266
+ left_child=1 -1 5 -3 -5 -2
267
+ right_child=2 3 -4 4 -6 -7
268
+ leaf_value=0.030890087269915469 -0.068844180687072695 0.067365934528544585 -0.07491155650147141 0.066556939476662982 0.065953825729960738 -0.069068423559230638
269
+ leaf_weight=1.0397322177886978 0.99393287301063438 1.5245727151632307 1.0391902029514311 0.934267297387123 5.3173656910657883 5.795934185385704
270
+ leaf_count=5 5 8 5 5 29 29
271
+ internal_value=6.69498e-05 0.0621266 -0.0698155 0.0663031 0.066044 -0.0690356
272
+ internal_weight=16.645 8.81594 7.82906 7.77621 6.25163 6.78987
273
+ internal_count=86 47 39 42 34 34
274
+ is_linear=0
275
+ shrinkage=0.05
276
+
277
+
278
+ Tree=14
279
+ num_leaves=7
280
+ num_cat=0
281
+ split_feature=2 11 8 12 9 12
282
+ split_gain=26.9382 0.459065 0.0105335 0.000704175 9.47011e-05 1.38494e-05
283
+ threshold=1.0000000180025095e-35 133.51370949074075 0.37980769230769235 0.065152412384244726 0.075080247396852748 0.057153336188971379
284
+ decision_type=2 2 2 2 2 2
285
+ left_child=1 -1 5 -3 -5 -2
286
+ right_child=2 3 -4 4 -6 -7
287
+ leaf_value=0.029651734623831839 -0.067590571418553 0.066237534135364193 -0.073249213876235661 0.065490928904136186 0.064935639733780637 -0.067795847197589632
288
+ leaf_weight=1.0292685776948944 0.96254490315914054 1.4742923229932783 1.0103661864995954 0.90279860794544209 5.1353192180395126 5.6141017973423004
289
+ leaf_count=5 5 8 5 5 29 29
290
+ internal_value=6.71579e-05 0.0609673 -0.068496 0.0652579 0.0650187 -0.0677658
291
+ internal_weight=16.1287 8.54168 7.58701 7.51241 6.03812 6.57665
292
+ internal_count=86 47 39 42 34 34
293
+ is_linear=0
294
+ shrinkage=0.05
295
+
296
+
297
+ Tree=15
298
+ num_leaves=7
299
+ num_cat=0
300
+ split_feature=7 1 12 11 9 8
301
+ split_gain=25.2841 0.466343 0.0135987 0.00208613 7.84636e-05 2.37464e-05
302
+ threshold=3112.1875000000005 2.5000000000000004 0.046585109941867327 141.79473379629633 0.075080247396852748 0.31009615384615391
303
+ decision_type=2 2 2 2 2 2
304
+ left_child=2 -2 -1 -3 -5 -4
305
+ right_child=1 3 5 4 -6 -7
306
+ leaf_value=-0.072893663688079216 0.027857376697344789 0.066425308757184637 -0.066629706487692036 0.064509006738265373 0.063997034156788757 -0.066375603717052159
307
+ leaf_weight=0.98681633174419481 0.99035722017288019 1.1084902882575987 5.2441673576831818 0.8715066760778426 5.2964922934770584 1.114899083971977
308
+ leaf_count=5 5 6 28 5 31 6
309
+ internal_value=6.71441e-05 0.0600471 -0.0674326 0.0644283 0.0640694 -0.0665852
310
+ internal_weight=15.6127 8.26685 7.34588 7.27649 6.168 6.35907
311
+ internal_count=86 47 39 42 36 34
312
+ is_linear=0
313
+ shrinkage=0.05
314
+
315
+
316
+ Tree=16
317
+ num_leaves=7
318
+ num_cat=0
319
+ split_feature=2 9 8 11 12 12
320
+ split_gain=23.6365 0.460809 0.0114327 0.00129081 0.000857083 1.66703e-05
321
+ threshold=1.0000000180025095e-35 0.075080247396852748 0.37980769230769235 141.79473379629633 0.065152412384244726 0.057153336188971379
322
+ decision_type=2 2 2 2 2 2
323
+ left_child=1 -1 5 -3 -5 -2
324
+ right_child=2 3 -4 4 -6 -7
325
+ leaf_value=0.028761524977802318 -0.065324699374286921 0.06537421778726113 -0.071401860938867565 0.064570157556829572 0.063147457310399377 -0.065557862254063778
326
+ leaf_weight=1.0873456448316572 0.89769710600376118 1.0709487497806547 0.95586602389812458 1.3890060782432554 4.4504676759243011 5.2488974630832672
327
+ leaf_count=6 5 6 5 8 27 29
328
+ internal_value=6.69732e-05 0.0590177 -0.0663149 0.0637785 0.0634859 -0.0655238
329
+ internal_weight=15.1002 7.99777 7.10246 6.91042 5.83947 6.14659
330
+ internal_count=86 47 39 41 35 34
331
+ is_linear=0
332
+ shrinkage=0.05
333
+
334
+
335
+ Tree=17
336
+ num_leaves=7
337
+ num_cat=0
338
+ split_feature=7 1 5 11 9 8
339
+ split_gain=22.2333 0.452603 0.01447 0.00133644 0.000225874 1.56853e-05
340
+ threshold=3112.1875000000005 2.5000000000000004 5.5000000000000009 141.79473379629633 0.075080247396852748 0.22390932420872542
341
+ decision_type=2 2 2 2 2 2
342
+ left_child=2 -2 -1 -3 -5 -4
343
+ right_child=1 3 5 4 -6 -7
344
+ leaf_value=-0.071224329883907991 0.025963412749295117 0.064405295726729775 -0.064573159584937589 0.063217045276053679 0.062323782790886573 -0.064388530194293372
345
+ leaf_weight=0.9326527565717696 0.9537997394800185 1.0336525738239286 4.3687743544578552 0.82667908072471608 4.91704460978508 1.5615205615758894
346
+ leaf_count=5 5 6 25 5 31 9
347
+ internal_value=6.6361e-05 0.0582118 -0.065435 0.0627502 0.0624523 -0.0645245
348
+ internal_weight=14.5941 7.73118 6.86295 6.77738 5.74372 5.93029
349
+ internal_count=86 47 39 42 36 34
350
+ is_linear=0
351
+ shrinkage=0.05
352
+
353
+
354
+ Tree=18
355
+ num_leaves=7
356
+ num_cat=0
357
+ split_feature=7 9 12 6 9 9
358
+ split_gain=20.8363 0.652884 0.0278199 0.0010596 0.000166491 0.00017995
359
+ threshold=3449.4166666666674 0.071839745922519613 0.050737547542749055 40049.000000000007 0.21825396825396828 0.075080247396852748
360
+ decision_type=2 2 2 2 2 2
361
+ left_child=1 2 -1 -2 5 -5
362
+ right_child=3 -3 -4 4 -6 -7
363
+ leaf_value=-0.071477255919176391 0.063450035666864163 -0.022026255105573586 -0.063623777802643541 0.062407425281717423 0.062381491662216286 0.06157488956704156
364
+ leaf_weight=1.4824470132589338 1.3250540494918821 1.0033586025238035 4.711548388004303 0.79630309343338002 1.2639052122831342 3.5102473795413971
365
+ leaf_count=8 8 6 28 5 8 23
366
+ internal_value=6.59451e-05 -0.0594424 -0.0655034 0.0621792 0.0618769 0.0617288
367
+ internal_weight=14.0929 7.19735 6.194 6.89551 5.57046 4.30655
368
+ internal_count=86 42 36 44 36 28
369
+ is_linear=0
370
+ shrinkage=0.05
371
+
372
+
373
+ Tree=19
374
+ num_leaves=7
375
+ num_cat=0
376
+ split_feature=7 3 12 3 11 9
377
+ split_gain=19.6055 0.444006 0.0104534 0.00103889 0.00063865 6.98296e-05
378
+ threshold=3112.1875000000005 0.63333333333333341 0.046585109941867327 0.68333333333333346 141.79473379629633 0.071279139270771077
379
+ decision_type=2 2 2 2 2 2
380
+ left_child=2 -2 -1 -3 -5 -4
381
+ right_child=1 3 5 4 -6 -7
382
+ leaf_value=-0.068717639224812194 0.025629335762716639 0.062741146854963084 -0.062759018789244531 0.062506077126058254 0.060952086412690371 -0.063256992222117198
383
+ leaf_weight=0.87429985404014576 0.99747399985790242 1.4439515471458433 4.6966153234243393 0.79297935962676991 3.9772752821445465 0.828120857477188
384
+ leaf_count=5 6 9 29 5 27 5
385
+ internal_value=6.8314e-05 0.0565956 -0.0636376 0.0615661 0.0612104 -0.0628337
386
+ internal_weight=13.6107 7.21168 6.39904 6.21421 4.77025 5.52474
387
+ internal_count=86 47 39 41 32 34
388
+ is_linear=0
389
+ shrinkage=0.05
390
+
391
+
392
+ Tree=20
393
+ num_leaves=7
394
+ num_cat=0
395
+ split_feature=2 11 7 3 6 9
396
+ split_gain=18.4871 0.422107 0.0177812 0.00075395 0.000542814 0.000113598
397
+ threshold=1.0000000180025095e-35 133.51370949074075 2936.421364560174 0.68333333333333346 50443.000000000007 0.087121212121212141
398
+ decision_type=2 2 2 2 2 2
399
+ left_child=1 -1 -2 4 -3 -5
400
+ right_child=2 3 -4 5 -6 -7
401
+ leaf_value=0.023900812353780387 -0.061982260484055023 0.062332936528495059 -0.070377612538269629 0.060846385615579639 0.060668994649296808 0.060159089984660935
402
+ leaf_weight=0.89601355791091908 5.4556252509355545 0.94887344539165486 0.71314486861228932 0.73183779418468464 1.0138113945722578 3.3682003915309906
403
+ leaf_count=5 35 6 4 5 7 24
404
+ internal_value=6.73833e-05 0.0559334 -0.0629528 0.0606675 0.0614734 0.0602818
405
+ internal_weight=13.1275 6.95874 6.16877 6.06272 1.96268 4.10004
406
+ internal_count=86 47 39 42 13 29
407
+ is_linear=0
408
+ shrinkage=0.05
409
+
410
+
411
+ Tree=21
412
+ num_leaves=7
413
+ num_cat=0
414
+ split_feature=7 1 5 11 3 9
415
+ split_gain=17.3496 0.44958 0.011074 0.00117389 0.00012922 5.21093e-05
416
+ threshold=3112.1875000000005 2.5000000000000004 5.5000000000000009 141.79473379629633 0.63333333333333341 0.071279139270771077
417
+ decision_type=2 2 2 2 2 2
418
+ left_child=2 -2 -1 -3 -5 -4
419
+ right_child=1 3 5 4 -6 -7
420
+ leaf_value=-0.06756471513158957 0.021553084846690478 0.061767345011491871 -0.061249842101192158 0.060437828583681366 0.059710418143377145 -0.061696580908138204
421
+ leaf_weight=0.82338908314704884 0.86779353022575367 0.91309693455696095 4.3476990461349487 0.71383780241012562 4.2189132273197174 0.76806645095348347
422
+ leaf_count=5 5 6 29 5 31 5
423
+ internal_value=6.67108e-05 0.0551354 -0.0621831 0.0601205 0.0598157 -0.0613169
424
+ internal_weight=12.6528 6.71364 5.93915 5.84585 4.93275 5.11577
425
+ internal_count=86 47 39 42 36 34
426
+ is_linear=0
427
+ shrinkage=0.05
428
+
429
+
430
+ Tree=22
431
+ num_leaves=7
432
+ num_cat=0
433
+ split_feature=7 9 12 6 9 9
434
+ split_gain=16.3951 0.572651 0.0250496 0.000768526 0.000137849 7.89605e-05
435
+ threshold=3449.4166666666674 0.071839745922519613 0.050737547542749055 40049.000000000007 0.21825396825396828 0.075080247396852748
436
+ decision_type=2 2 2 2 2 2
437
+ left_child=1 2 -1 -2 5 -5
438
+ right_child=3 -3 -4 4 -6 -7
439
+ leaf_value=-0.068535316052522055 0.060816213959984794 -0.019438194723139134 -0.060602147086615446 0.059719033650448367 0.059873001198438448 0.059122713194765142
440
+ leaf_weight=1.3200946748256681 1.1560638695955274 0.89922252297401417 4.0412775725126266 0.68117281794548068 1.0909055322408674 2.9999326914548874
441
+ leaf_count=8 8 6 28 5 8 23
442
+ internal_value=6.59541e-05 -0.0563625 -0.0625555 0.0596596 0.0593794 0.0592331
443
+ internal_weight=12.1887 6.26059 5.36137 5.92807 4.77201 3.68111
444
+ internal_count=86 42 36 44 36 28
445
+ is_linear=0
446
+ shrinkage=0.05
447
+
448
+
449
+ Tree=23
450
+ num_leaves=7
451
+ num_cat=0
452
+ split_feature=2 9 7 3 6 11
453
+ split_gain=15.4748 0.409392 0.0152171 0.00084384 0.000832987 0.000542181
454
+ threshold=1.0000000180025095e-35 0.075080247396852748 2936.421364560174 0.68333333333333346 50443.000000000007 141.79473379629633
455
+ decision_type=2 2 2 2 2 2
456
+ left_child=1 -1 -2 4 -3 -5
457
+ right_child=2 3 -4 5 -6 -7
458
+ leaf_value=0.022590831551048782 -0.060005776826580329 0.06106710650507513 -0.068132524420055657 0.060068144672354024 0.058892339480384651 0.058505384784072391
459
+ leaf_weight=0.88774001598358143 4.8577902466058731 1.0309564769268034 0.65351083874702443 0.68879798054695163 0.76853059977293003 2.8573894053697586
460
+ leaf_count=6 35 7 4 5 6 23
461
+ internal_value=6.80767e-05 0.0540346 -0.0609694 0.0592564 0.0601383 0.0588089
462
+ internal_weight=11.7447 6.23341 5.5113 5.34567 1.79949 3.54619
463
+ internal_count=86 47 39 41 13 28
464
+ is_linear=0
465
+ shrinkage=0.05
466
+
467
+
468
+ Tree=24
469
+ num_leaves=7
470
+ num_cat=0
471
+ split_feature=7 9 12 6 9 9
472
+ split_gain=14.5655 0.541723 0.0216997 0.000501072 9.78822e-05 0.000128185
473
+ threshold=3449.4166666666674 0.071839745922519613 0.050737547542749055 40049.000000000007 0.075080247396852748 0.21825396825396828
474
+ decision_type=2 2 2 2 2 2
475
+ left_child=1 2 -1 -2 -5 -6
476
+ right_child=3 -3 -4 4 5 -7
477
+ leaf_value=-0.067024921199474077 0.059588834455201412 -0.018346171426203251 -0.05938954185150102 0.058951152261286548 0.058107946346887443 0.058767401643471141
478
+ leaf_weight=1.2402108311653135 1.0672495663166044 0.86150559782981861 3.726663663983345 0.64380145072937001 2.760124303400517 1.0052870362997053
479
+ leaf_count=8 8 6 28 5 23 8
480
+ internal_value=6.69666e-05 -0.0549476 -0.0612961 0.0586167 0.0583814 0.058284
481
+ internal_weight=11.3048 5.82838 4.96687 5.47646 4.40921 3.76541
482
+ internal_count=86 42 36 44 36 31
483
+ is_linear=0
484
+ shrinkage=0.05
485
+
486
+
487
+ Tree=25
488
+ num_leaves=7
489
+ num_cat=0
490
+ split_feature=7 8 5 3 6 11
491
+ split_gain=13.7558 0.413425 0.00826678 0.000784093 0.00100285 0.000358132
492
+ threshold=3112.1875000000005 0.40833333333333338 5.5000000000000009 0.68333333333333346 50443.000000000007 141.79473379629633
493
+ decision_type=2 2 2 2 2 2
494
+ left_child=2 3 -1 4 -2 -5
495
+ right_child=1 -3 -4 5 -6 -7
496
+ leaf_value=-0.064760998748160811 0.060411037092192879 0.020204062489322272 -0.058976871071604077 0.058942640863666663 0.057967853146372289 0.057620131955101794
497
+ leaf_weight=0.71913889795541752 0.85121817886829354 0.83034586906433094 4.3807578086853027 0.63516688346862826 0.82912010699510574 2.6377299129962921
498
+ leaf_count=5 6 6 34 5 7 23
499
+ internal_value=6.89911e-05 0.0528542 -0.0597925 0.0583275 0.0592055 0.0578768
500
+ internal_weight=10.8835 5.78358 5.0999 4.95324 1.68034 3.2729
501
+ internal_count=86 47 39 41 13 28
502
+ is_linear=0
503
+ shrinkage=0.05
504
+
505
+
506
+ Tree=26
507
+ num_leaves=7
508
+ num_cat=0
509
+ split_feature=2 12 7 6 9 3
510
+ split_gain=13.0195 0.386766 0.0129472 0.000663642 0.000231849 0.000100648
511
+ threshold=1.0000000180025095e-35 0.058858058382239557 2936.421364560174 42986.000000000007 0.071279139270771077 0.68333333333333346
512
+ decision_type=2 2 2 2 2 2
513
+ left_child=1 -1 4 -3 -2 -5
514
+ right_child=2 3 -4 5 -6 -7
515
+ leaf_value=0.020559904055127788 -0.058251627610288348 0.059187580586970483 -0.066266872347715874 0.05797390785239085 -0.059270945587050296 0.057422964387858488
516
+ leaf_weight=0.81472939252853382 3.645292304456234 0.7722501829266547 0.59667611867189396 1.1771411001682284 0.65865901857614506 2.8025532513856888
517
+ leaf_count=6 30 6 4 10 5 25
518
+ internal_value=6.77682e-05 0.0523891 -0.0593645 0.0578462 -0.0584076 0.0575859
519
+ internal_weight=10.4673 5.56667 4.90063 4.75194 4.30395 3.97969
520
+ internal_count=86 47 39 41 35 35
521
+ is_linear=0
522
+ shrinkage=0.05
523
+
524
+
525
+ Tree=27
526
+ num_leaves=7
527
+ num_cat=0
528
+ split_feature=7 9 12 6 9 9
529
+ split_gain=12.2925 0.489163 0.0188603 0.000383646 8.37721e-05 6.84616e-05
530
+ threshold=3449.4166666666674 0.071839745922519613 0.050737547542749055 40049.000000000007 0.075080247396852748 0.21825396825396828
531
+ decision_type=2 2 2 2 2 2
532
+ left_child=1 2 -1 -2 -5 -6
533
+ right_child=3 -3 -4 4 5 -7
534
+ leaf_value=-0.065351573347258926 0.058201978148554413 -0.017119792068844602 -0.057861048104718864 0.057642731793756244 0.056851957818626599 0.057365219746097679
535
+ leaf_weight=1.1290041878819463 0.95821357518434513 0.79274052381515492 3.2869395241141319 0.57451695203781161 2.436129823327065 0.88597673177719105
536
+ leaf_count=8 8 6 28 5 23 8
537
+ internal_value=6.65903e-05 -0.053284 -0.0597761 0.0573057 0.0570852 0.0569888
538
+ internal_weight=10.0635 5.20868 4.41594 4.85484 3.89662 3.32211
539
+ internal_count=86 42 36 44 36 31
540
+ is_linear=0
541
+ shrinkage=0.05
542
+
543
+
544
+ Tree=28
545
+ num_leaves=7
546
+ num_cat=0
547
+ split_feature=7 1 5 11 9 7
548
+ split_gain=11.6086 0.397906 0.00725715 0.000617763 0.0002746 0.000130696
549
+ threshold=3112.1875000000005 2.5000000000000004 5.5000000000000009 141.79473379629633 0.071279139270771077 3723.3125000000005
550
+ decision_type=2 2 2 2 2 2
551
+ left_child=2 -2 -1 -3 -4 -5
552
+ right_child=1 3 4 5 -6 -7
553
+ leaf_value=-0.063287458024544704 0.016795342489476839 0.058297174904840682 -0.057390464271318914 0.05712056058143096 -0.058537814624013043 0.056485396566092597
554
+ leaf_weight=0.64899222552776326 0.71674503386020649 0.71003039181232441 3.2527691423892979 1.1892860233783724 0.62106423079967488 2.538827121257782
555
+ leaf_count=5 5 6 29 11 5 25
556
+ internal_value=6.85434e-05 0.0513629 -0.0583942 0.0569455 -0.0575744 0.056688
557
+ internal_weight=9.67771 5.15489 4.52283 4.43814 3.87383 3.72811
558
+ internal_count=86 47 39 42 34 36
559
+ is_linear=0
560
+ shrinkage=0.05
561
+
562
+
563
+ Tree=29
564
+ num_leaves=7
565
+ num_cat=0
566
+ split_feature=2 9 7 3 6 9
567
+ split_gain=11.0019 0.370817 0.01088 0.000682509 0.000701566 0.000256016
568
+ threshold=1.0000000180025095e-35 0.075080247396852748 2936.421364560174 0.68333333333333346 50443.000000000007 0.071279139270771077
569
+ decision_type=2 2 2 2 2 2
570
+ left_child=1 -1 5 4 -3 -2
571
+ right_child=2 3 -4 -5 -6 -7
572
+ leaf_value=0.018486343351054913 -0.056923856725493785 0.058520334769237597 -0.064665982612492262 0.05626769846211898 0.056286448244358883 -0.058052796916793395
573
+ leaf_weight=0.74618197977542866 3.2023785859346394 0.85925551503896691 0.54325389117002476 2.7559519186615944 0.59474007040262222 0.59558508545160282
574
+ leaf_count=6 30 7 4 28 6 5
575
+ internal_value=6.75255e-05 0.0509722 -0.0580476 0.0567301 0.0576066 -0.0571009
576
+ internal_weight=9.29735 4.95613 4.34122 4.20995 1.454 3.79796
577
+ internal_count=86 47 39 41 13 35
578
+ is_linear=0
579
+ shrinkage=0.05
580
+
581
+
582
+ Tree=30
583
+ num_leaves=7
584
+ num_cat=0
585
+ split_feature=7 9 12 6 9 9
586
+ split_gain=10.4161 0.44449 0.01614 0.000279951 0.000107893 5.28035e-05
587
+ threshold=3449.4166666666674 0.071839745922519613 0.050737547542749055 40049.000000000007 0.075080247396852748 0.21825396825396828
588
+ decision_type=2 2 2 2 2 2
589
+ left_child=1 2 -1 -2 -5 -6
590
+ right_child=3 -3 -4 4 5 -7
591
+ leaf_value=-0.063912827952955498 0.057039225047386011 -0.015972305675681888 -0.056610658318094302 0.056691086786257221 0.055780573066398942 0.056260987745907258
592
+ leaf_weight=1.0254787206649782 0.85380606353282917 0.72786590456962574 2.8874571472406387 0.51999834924936328 2.134867444634438 0.78128480911254872
593
+ leaf_count=8 8 6 28 5 23 8
594
+ internal_value=6.62766e-05 -0.0518505 -0.0585244 0.0562289 0.0560276 0.0559093
595
+ internal_weight=8.93076 4.6408 3.91294 4.28996 3.43615 2.91615
596
+ internal_count=86 42 36 44 36 31
597
+ is_linear=0
598
+ shrinkage=0.05
599
+
600
+
601
+ Tree=31
602
+ num_leaves=7
603
+ num_cat=0
604
+ split_feature=7 1 5 7 9 11
605
+ split_gain=9.83589 0.374511 0.00629963 0.000526358 0.000278143 9.52215e-05
606
+ threshold=3112.1875000000005 2.5000000000000004 5.5000000000000009 3640.0756302521013 0.071279139270771077 141.79473379629633
607
+ decision_type=2 2 2 2 2 2
608
+ left_child=2 -2 -1 -3 -4 -5
609
+ right_child=1 3 4 5 -6 -7
610
+ leaf_value=-0.062038035970580635 0.015312301405758769 0.057267395266565102 -0.0562204121958855 0.056335163777748776 -0.057438264315309052 0.055580914687308039
611
+ leaf_weight=0.58454194664955172 0.66288314014673222 0.63404047489166293 2.852705761790276 0.49217656999826465 0.56103963404893864 2.7935361713171005
612
+ leaf_count=5 5 6 29 5 5 31
613
+ internal_value=6.81728e-05 0.0500704 -0.0572418 0.0559484 -0.0564206 0.0556939
614
+ internal_weight=8.58092 4.58264 3.99829 3.91975 3.41375 3.28571
615
+ internal_count=86 47 39 42 34 36
616
+ is_linear=0
617
+ shrinkage=0.05
618
+
619
+
620
+ Tree=32
621
+ num_leaves=7
622
+ num_cat=0
623
+ split_feature=2 11 7 3 6 9
624
+ split_gain=9.33088 0.349815 0.00907291 0.000354295 0.000385433 0.000256073
625
+ threshold=1.0000000180025095e-35 133.51370949074075 2936.421364560174 0.68333333333333346 50443.000000000007 0.071279139270771077
626
+ decision_type=2 2 2 2 2 2
627
+ left_child=1 -1 5 4 -3 -2
628
+ right_child=2 3 -4 -5 -6 -7
629
+ leaf_value=0.015926168801723522 -0.055832119263564496 0.05713410010864043 -0.063286192030390473 0.055256561966569744 0.055386332730094849 -0.057023546364382338
630
+ leaf_weight=0.65211432427167881 2.8032477051019677 0.64919306337833427 0.49360075592994679 2.4881960600614548 0.61358394473791122 0.5374602898955344
631
+ leaf_count=5 30 6 4 29 7 5
632
+ internal_value=6.71091e-05 0.0497265 -0.0569587 0.0556027 0.0562849 -0.0560238
633
+ internal_weight=8.2374 4.40309 3.83431 3.75097 1.26278 3.34071
634
+ internal_count=86 47 39 42 13 35
635
+ is_linear=0
636
+ shrinkage=0.05
637
+
638
+
639
+ Tree=33
640
+ num_leaves=7
641
+ num_cat=0
642
+ split_feature=7 7 12 6 9 9
643
+ split_gain=8.83465 0.495924 0.00632183 0.000252359 0.000200043 6.18914e-05
644
+ threshold=3449.4166666666674 3112.1875000000005 0.046585109941867327 40049.000000000007 0.071279139270771077 0.075080247396852748
645
+ decision_type=2 2 2 2 2 2
646
+ left_child=1 2 -1 -2 -4 -5
647
+ right_child=3 -3 4 5 -6 -7
648
+ leaf_value=-0.061556231231573035 0.056137224344672776 -0.001381895729157019 -0.055556071395645848 0.055656976219855096 -0.056634677162818239 0.055024700516101088
649
+ leaf_weight=0.54798562079668078 0.76531849801540408 0.45721171796321858 2.6089930459856991 0.45611468702554736 0.51466943323612202 2.5557717457413673
650
+ leaf_count=5 8 3 29 5 5 31
651
+ internal_value=6.60386e-05 -0.0504879 -0.0566028 0.0553265 -0.0557338 0.0551205
652
+ internal_weight=7.90606 4.12886 3.67165 3.7772 3.12366 3.01189
653
+ internal_count=86 42 39 44 34 36
654
+ is_linear=0
655
+ shrinkage=0.05
656
+
657
+
658
+ Tree=34
659
+ num_leaves=7
660
+ num_cat=0
661
+ split_feature=2 9 7 7 9 0
662
+ split_gain=8.35001 0.351499 0.00832904 0.000696102 0.000184189 0.000182014
663
+ threshold=1.0000000180025095e-35 0.075080247396852748 2936.421364560174 3478.8119047619052 0.071279139270771077 3.5000000000000004
664
+ decision_type=2 2 2 2 2 2
665
+ left_child=1 -1 4 -3 -2 -5
666
+ right_child=2 3 -4 5 -6 -7
667
+ leaf_value=0.014947449539177768 -0.055214120163731763 0.056963217459914332 -0.062580926893981162 0.055517369242061111 -0.056269756118548744 0.054678016325414684
668
+ leaf_weight=0.64245630055665959 2.5623199343681344 0.5072332322597507 0.46298509091138829 0.96613904088735614 0.49266319721937168 1.9485112279653547
669
+ leaf_count=6 30 5 4 11 5 25
670
+ internal_value=6.62901e-05 0.0488825 -0.0563315 0.0552537 -0.0553844 0.0549562
671
+ internal_weight=7.58231 4.06434 3.51797 3.42188 3.05498 2.91465
672
+ internal_count=86 47 39 41 35 36
673
+ is_linear=0
674
+ shrinkage=0.05
675
+
676
+
677
+ Tree=35
678
+ num_leaves=7
679
+ num_cat=0
680
+ split_feature=7 7 5 6 9 9
681
+ split_gain=7.928 0.465997 0.00627062 0.000168309 0.000144454 8.61189e-05
682
+ threshold=3449.4166666666674 3112.1875000000005 5.5000000000000009 40049.000000000007 0.071279139270771077 0.075080247396852748
683
+ decision_type=2 2 2 2 2 2
684
+ left_child=1 2 -1 -2 -4 -5
685
+ right_child=3 -3 4 5 -6 -7
686
+ leaf_value=-0.06112951982230596 0.055489765832188481 -0.0014456136893101455 -0.054969160357358007 0.055271421843250457 -0.05592704783840783 0.054502127112148469
687
+ leaf_weight=0.51339258998632509 0.70041552931070361 0.44213592261075962 2.3835200518369679 0.43097247928380999 0.47143466770648945 2.3337739482522011
688
+ leaf_count=5 8 3 29 5 5 31
689
+ internal_value=6.50601e-05 -0.0497073 -0.0560422 0.0547974 -0.0551273 0.054622
690
+ internal_weight=7.27565 3.81048 3.36835 3.46516 2.85495 2.76475
691
+ internal_count=86 42 39 44 34 36
692
+ is_linear=0
693
+ shrinkage=0.05
694
+
695
+
696
+ Tree=36
697
+ num_leaves=7
698
+ num_cat=0
699
+ split_feature=7 4 7 9 6 9
700
+ split_gain=7.48285 0.470136 0.0076194 0.000162971 0.000142931 7.29879e-05
701
+ threshold=3449.4166666666674 1.0000000180025095e-35 2936.421364560174 0.071839745922519613 40049.000000000007 0.075080247396852748
702
+ decision_type=2 2 2 2 2 2
703
+ left_child=1 2 3 -1 -2 -6
704
+ right_child=4 -3 -4 -5 5 -7
705
+ leaf_value=-0.054668841074267054 0.055194448155474175 -0.0006523767316843794 -0.061938902913771236 -0.055792750812624518 0.054988023932388025 0.054263649587207231
706
+ leaf_weight=2.4181659296154989 0.66973040252923999 0.43949657678604115 0.43420322984457005 0.37218537181615818 0.41201303899288211 2.2294638529419899
707
+ leaf_count=31 8 3 4 4 5 31
708
+ internal_value=6.53258e-05 -0.0491654 -0.0557775 -0.0548188 0.054542 0.0543766
709
+ internal_weight=6.97526 3.66405 3.22455 2.79035 3.31121 2.64148
710
+ internal_count=86 42 39 35 44 36
711
+ is_linear=0
712
+ shrinkage=0.05
713
+
714
+
715
+ Tree=37
716
+ num_leaves=7
717
+ num_cat=0
718
+ split_feature=7 8 3 7 0 9
719
+ split_gain=7.08513 0.625026 0.00940651 0.000726114 8.40313e-05 6.44824e-05
720
+ threshold=3112.1875000000005 0.48529411764705888 0.00030309239974547856 3478.8119047619052 3.5000000000000004 0.069618762407887225
721
+ decision_type=2 2 2 2 2 2
722
+ left_child=2 3 5 -2 -5 -1
723
+ right_child=1 -3 -4 4 -6 -7
724
+ leaf_value=-0.054395364925094974 0.05639705795332367 -0.01565991629893191 -0.063037368840106148 0.054678313320029787 0.054080703010607023 -0.055026815765338855
725
+ leaf_weight=2.2251070216298112 0.46460698544979173 0.3510647416114806 0.36706323921680439 0.84411208331584964 1.9404393583536146 0.49407067149877537
726
+ leaf_count=30 5 3 3 11 28 6
727
+ internal_value=6.55694e-05 0.0477192 -0.0555243 0.0545672 0.0542619 -0.0545101
728
+ internal_weight=6.68646 3.60022 3.08624 3.24916 2.78455 2.71918
729
+ internal_count=86 47 39 44 39 36
730
+ is_linear=0
731
+ shrinkage=0.05
732
+
733
+
734
+ Tree=38
735
+ num_leaves=7
736
+ num_cat=0
737
+ split_feature=7 1 8 9 6 9
738
+ split_gain=6.71073 0.44171 0.00833921 0.000117938 9.75054e-05 6.51949e-05
739
+ threshold=3449.4166666666674 2.5000000000000004 0.48529411764705888 0.071839745922519613 40049.000000000007 0.075080247396852748
740
+ decision_type=2 2 2 2 2 2
741
+ left_child=1 2 3 -1 -2 -6
742
+ right_child=4 -3 -4 -5 5 -7
743
+ leaf_value=-0.054176317691610465 0.054654224266567131 -0.00070694118547682931 -0.062643158321476405 -0.055174794917573503 0.05454790994267375 0.05383536400562642
744
+ leaf_weight=2.2735335305333151 0.61183255910873446 0.42434006929397572 0.33883924037218083 0.33996777236461628 0.38107533752918277 2.0368824377655983
745
+ leaf_count=32 8 3 3 4 5 31
746
+ internal_value=6.66723e-05 -0.0484071 -0.055263 -0.0543062 0.0540903 0.0539477
747
+ internal_weight=6.40647 3.37668 2.95234 2.6135 3.02979 2.41796
748
+ internal_count=86 42 39 36 44 36
749
+ is_linear=0
750
+ shrinkage=0.05
751
+
752
+
753
+ Tree=39
754
+ num_leaves=7
755
+ num_cat=0
756
+ split_feature=7 8 3 7 0 9
757
+ split_gain=6.36573 0.578683 0.00839249 0.000692171 6.10424e-05 4.69022e-05
758
+ threshold=3112.1875000000005 0.48529411764705888 0.00030309239974547856 3478.8119047619052 3.5000000000000004 0.069618762407887225
759
+ decision_type=2 2 2 2 2 2
760
+ left_child=2 3 5 -2 -5 -1
761
+ right_child=1 -3 -4 4 -6 -7
762
+ leaf_value=-0.053941094211068685 0.055983462634242864 -0.014797020999098879 -0.062355730228598985 0.054196430034086966 0.053663191066265548 -0.054504693631837665
763
+ leaf_weight=2.0288115218281755 0.43503814563155252 0.33912311494350422 0.34604882448911656 0.77044664323329959 1.7689581885933874 0.45124471187591542
764
+ leaf_count=30 5 3 3 11 28 6
765
+ internal_value=6.69309e-05 0.0470853 -0.0550614 0.0541407 0.053825 -0.0540436
766
+ internal_weight=6.13967 3.31357 2.82611 2.97444 2.5394 2.48006
767
+ internal_count=86 47 39 44 39 36
768
+ is_linear=0
769
+ shrinkage=0.05
770
+
771
+
772
+ Tree=40
773
+ num_leaves=7
774
+ num_cat=0
775
+ split_feature=7 1 8 9 6 9
776
+ split_gain=6.0239 0.415509 0.00716593 8.37942e-05 6.58231e-05 5.87717e-05
777
+ threshold=3449.4166666666674 2.5000000000000004 0.48529411764705888 0.071839745922519613 40049.000000000007 0.075080247396852748
778
+ decision_type=2 2 2 2 2 2
779
+ left_child=1 2 3 -1 -2 -6
780
+ right_child=4 -3 -4 -5 5 -7
781
+ leaf_value=-0.053747198866373358 0.054174246102799986 -0.00074871437985491265 -0.061848534735531473 -0.054628365896544698 0.054158817872469428 0.053454524957827709
782
+ leaf_weight=2.0726009085774435 0.55836819112300906 0.40939591825008381 0.31838491559028614 0.31017342954874028 0.3523313328623775 1.8596719168126581
783
+ leaf_count=32 8 3 3 4 5 31
784
+ internal_value=6.80133e-05 -0.0476889 -0.0548033 -0.0538619 0.0536892 0.0535667
785
+ internal_weight=5.88093 3.11056 2.70116 2.38277 2.77037 2.212
786
+ internal_count=86 42 39 36 44 36
787
+ is_linear=0
788
+ shrinkage=0.05
789
+
790
+
791
+ Tree=41
792
+ num_leaves=7
793
+ num_cat=0
794
+ split_feature=7 8 3 7 0 9
795
+ split_gain=5.7252 0.536242 0.00747846 0.000655393 4.44475e-05 3.41942e-05
796
+ threshold=3112.1875000000005 0.48529411764705888 0.00030309239974547856 3478.8119047619052 3.5000000000000004 0.069618762407887225
797
+ decision_type=2 2 2 2 2 2
798
+ left_child=2 3 5 -2 -5 -1
799
+ right_child=1 -3 -4 4 -6 -7
800
+ leaf_value=-0.053537010646773124 0.055610610534127497 -0.013985263998826523 -0.061727170553975308 0.053767553137312941 0.053290985516214889 -0.054040869042317564
801
+ leaf_weight=1.8483116999268538 0.40735856071114618 0.32721980661153782 0.32622210681438435 0.70258275792002711 1.6113706566393373 0.41173452883958805
802
+ leaf_count=30 5 3 3 11 28 6
803
+ internal_value=6.82449e-05 0.0464896 -0.0546503 0.0537613 0.0534357 -0.0536288
804
+ internal_weight=5.6348 3.04853 2.58627 2.72131 2.31395 2.26005
805
+ internal_count=86 47 39 44 39 36
806
+ is_linear=0
807
+ shrinkage=0.05
808
+
809
+
810
+ Tree=42
811
+ num_leaves=7
812
+ num_cat=0
813
+ split_feature=7 7 3 6 9 9
814
+ split_gain=5.41202 0.394171 0.00641111 4.38394e-05 5.48303e-05 2.91248e-05
815
+ threshold=3449.4166666666674 3112.1875000000005 0.00030309239974547856 40049.000000000007 0.087121212121212141 0.069618762407887225
816
+ decision_type=2 2 2 2 2 2
817
+ left_child=1 2 5 -2 -5 -1
818
+ right_child=3 -3 -4 4 -6 -7
819
+ leaf_value=-0.053352663712986942 0.053747012822722956 -0.0005433541913502332 -0.06109198312579267 0.053718204717861685 0.053089683982466357 -0.053828554208506289
820
+ leaf_weight=1.7640897743403923 0.50911201909184489 0.3937300145626067 0.31345656886696804 0.44482421502471003 1.5777324028313158 0.39315973222255696
821
+ leaf_count=30 8 3 3 7 29 6
822
+ internal_value=6.93086e-05 -0.047006 -0.0544103 0.0533323 0.0532279 -0.0534394
823
+ internal_weight=5.3961 2.86444 2.47071 2.53167 2.02256 2.15725
824
+ internal_count=86 42 39 44 36 36
825
+ is_linear=0
826
+ shrinkage=0.05
827
+
828
+
829
+ Tree=43
830
+ num_leaves=7
831
+ num_cat=0
832
+ split_feature=2 9 8 7 9 1
833
+ split_gain=5.14676 0.416517 0.00643507 0.000615443 5.01119e-05 3.57671e-05
834
+ threshold=1.0000000180025095e-35 0.071279139270771077 0.48529411764705888 3478.8119047619052 0.071839745922519613 5.0000000000000009
835
+ decision_type=2 2 2 2 2 2
836
+ left_child=1 -1 4 -3 -2 -5
837
+ right_child=2 3 -4 5 -6 -7
838
+ leaf_value=-0.0032927725542232326 -0.053189321076280861 0.05527217636602412 -0.061192203606070542 0.053330110339481311 -0.053920202121437104 0.052907016543222808
839
+ leaf_weight=0.37335796654224429 1.8011869229376323 0.38144064322114024 0.29376778006553639 0.86185404658317599 0.26963154226541508 1.1881534345448015
840
+ leaf_count=4 32 5 3 15 4 23
841
+ internal_value=6.95056e-05 0.0458777 -0.0542669 0.053428 -0.0532845 0.0530849
842
+ internal_weight=5.16939 2.80481 2.36459 2.43145 2.07082 2.05001
843
+ internal_count=86 47 39 43 36 38
844
+ is_linear=0
845
+ shrinkage=0.05
846
+
847
+
848
+ Tree=44
849
+ num_leaves=7
850
+ num_cat=0
851
+ split_feature=7 9 12 9 0 4
852
+ split_gain=4.87065 0.373455 0.00759387 4.27489e-05 4.57402e-05 4.94146e-05
853
+ threshold=3449.4166666666674 0.071839745922519613 0.050737547542749055 0.075080247396852748 3.5000000000000004 1.0000000180025095e-35
854
+ decision_type=2 2 2 2 2 2
855
+ left_child=1 2 -1 -2 5 -5
856
+ right_child=3 -3 -4 4 -6 -7
857
+ leaf_value=-0.059481924618931842 0.05356627448655691 -0.0053640516220252593 -0.053057415019858271 0.052859926767791471 0.052761884723875802 0.053715583084513518
858
+ leaf_weight=0.65957568958401758 0.3061713203787807 0.45867507904767979 1.5198521241545675 0.35486402362585101 1.3312645144760606 0.32168889418244362
859
+ leaf_count=8 5 6 28 7 27 5
860
+ internal_value=6.98011e-05 -0.0463714 -0.0550017 0.0530159 0.052932 0.0532668
861
+ internal_weight=4.95209 2.6381 2.17943 2.31399 2.00782 0.676553
862
+ internal_count=86 42 36 44 39 12
863
+ is_linear=0
864
+ shrinkage=0.05
865
+
866
+
867
+ Tree=45
868
+ num_leaves=7
869
+ num_cat=0
870
+ split_feature=7 8 3 7 9 0
871
+ split_gain=4.63736 0.476236 0.00485685 0.000582061 2.96056e-05 2.16139e-05
872
+ threshold=3112.1875000000005 0.48529411764705888 0.00030309239974547856 3478.8119047619052 0.069618762407887225 3.5000000000000004
873
+ decision_type=2 2 2 2 2 2
874
+ left_child=2 3 4 -2 -1 -5
875
+ right_child=1 -3 -4 5 -6 -7
876
+ leaf_value=-0.052853755533095376 0.054985344829039356 -0.013782752907821267 -0.059935001183145403 0.053043608220952276 -0.053363596419050252 0.05267888921088263
877
+ leaf_weight=1.5297555290162566 0.35795415192842561 0.30096763372421254 0.28671444952487934 0.58293151110410679 0.34985763952136029 1.3399863652884958
878
+ leaf_count=30 5 3 3 11 6 28
879
+ internal_value=7.08982e-05 0.0453335 -0.0538733 0.0531341 -0.0529487 0.0527895
880
+ internal_weight=4.74817 2.58184 2.16633 2.28087 1.87961 1.92292
881
+ internal_count=86 47 39 44 36 39
882
+ is_linear=0
883
+ shrinkage=0.05
884
+
885
+
886
+ Tree=46
887
+ num_leaves=7
888
+ num_cat=0
889
+ split_feature=2 9 8 7 9 4
890
+ split_gain=4.40922 0.447484 0.00471205 0.000496091 5.14602e-05 2.4916e-05
891
+ threshold=1.0000000180025095e-35 0.065591397849462371 0.48529411764705888 3478.8119047619052 0.071839745922519613 1.0000000180025095e-35
892
+ decision_type=2 2 2 2 2 2
893
+ left_child=1 -1 4 -3 -2 -5
894
+ right_child=2 3 -4 5 -6 -7
895
+ leaf_value=-0.012584332341268274 -0.05271689598511281 0.054724538458758742 -0.059953959409466628 0.052504546177679447 -0.053497216445920016 0.0528828969447558
896
+ leaf_weight=0.29559918865561519 1.5617414489388468 0.34207821637392077 0.26570267975330342 1.1270508877933023 0.24433936551213253 0.70879586413502682
897
+ leaf_count=3 32 5 3 25 4 14
898
+ internal_value=7.19562e-05 0.0451415 -0.0537371 0.0529764 -0.0528225 0.0526506
899
+ internal_weight=4.54531 2.47352 2.07178 2.17792 1.80608 1.83585
900
+ internal_count=86 47 39 44 36 39
901
+ is_linear=0
902
+ shrinkage=0.05
903
+
904
+
905
+ Tree=47
906
+ num_leaves=7
907
+ num_cat=0
908
+ split_feature=7 8 3 7 9 6
909
+ split_gain=4.17468 0.43328 0.00415466 0.00042322 2.16753e-05 1.48204e-05
910
+ threshold=3112.1875000000005 0.48529411764705888 0.00030309239974547856 3478.8119047619052 0.069618762407887225 40049.000000000007
911
+ decision_type=2 2 2 2 2 2
912
+ left_child=2 3 4 -2 -1 -5
913
+ right_child=1 -3 -4 5 -6 -7
914
+ leaf_value=-0.052566765251834173 0.054478261217956375 -0.012068978077755697 -0.059337625153044499 0.052811538560065069 -0.053024003134294333 0.052446238174248261
915
+ leaf_weight=1.3909137547016142 0.32682865485549051 0.2935192547738551 0.26893061771988858 0.34596097096800793 0.31855252012610424 1.406175013631582
916
+ leaf_count=30 5 3 3 7 6 32
917
+ internal_value=7.29575e-05 0.0447977 -0.0535608 0.0528265 -0.052652 0.0525184
918
+ internal_weight=4.35088 2.37248 1.9784 2.07896 1.70947 1.75214
919
+ internal_count=86 47 39 44 36 39
920
+ is_linear=0
921
+ shrinkage=0.05
922
+
923
+
924
+ Tree=48
925
+ num_leaves=7
926
+ num_cat=0
927
+ split_feature=7 9 12 8 0 9
928
+ split_gain=3.97248 0.585256 0.00526959 6.08468e-05 2.74167e-05 1.91699e-05
929
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 0.071279139270771077
930
+ decision_type=2 2 2 2 2 2
931
+ left_child=1 2 -1 4 -2 -4
932
+ right_child=3 -3 5 -5 -6 -7
933
+ leaf_value=-0.058227599861785932 0.052704543358393807 0.029445810283716289 -0.052472795418974441 0.053195680650356104 0.052255735231464741 -0.052989089446205645
934
+ leaf_weight=0.57319482415914569 0.47649280354380597 0.23371871188282956 1.2117787078022955 0.26804463565349568 1.1903298236429689 0.21111251413822163
935
+ leaf_count=8 10 3 27 5 29 4
936
+ internal_value=7.39349e-05 -0.0454146 -0.0541799 0.0524965 0.052384 -0.0525494
937
+ internal_weight=4.16467 2.2298 1.99609 1.93487 1.66682 1.42289
938
+ internal_count=86 42 39 44 39 31
939
+ is_linear=0
940
+ shrinkage=0.05
941
+
942
+
943
+ Tree=49
944
+ num_leaves=7
945
+ num_cat=0
946
+ split_feature=2 9 8 7 9 4
947
+ split_gain=3.77277 0.494401 0.00347542 0.000339162 3.96033e-05 1.39968e-05
948
+ threshold=1.0000000180025095e-35 0.056251581878005573 0.48529411764705888 3478.8119047619052 0.071839745922519613 1.0000000180025095e-35
949
+ decision_type=2 2 2 2 2 2
950
+ left_child=1 -1 4 -3 -2 -5
951
+ right_child=2 3 -4 5 -6 -7
952
+ leaf_value=-0.025740498200464142 -0.052317830796601586 0.054115885835263458 -0.05887891549443755 0.052179560241072731 -0.053046725156541179 0.052480626276773648
953
+ leaf_weight=0.22477163374424058 1.3525291308760641 0.30179394781589497 0.24000708013772953 1.0285397768020628 0.21613523736596096 0.61801687628030766
954
+ leaf_count=2 32 5 3 26 4 14
955
+ internal_value=7.30238e-05 0.0444746 -0.0532756 0.052575 -0.0524183 0.0522926
956
+ internal_weight=3.98179 2.17312 1.80867 1.94835 1.56866 1.64656
957
+ internal_count=86 47 39 45 36 40
958
+ is_linear=0
959
+ shrinkage=0.05
960
+
961
+
962
+ Tree=50
963
+ num_leaves=7
964
+ num_cat=0
965
+ split_feature=7 9 12 8 0 9
966
+ split_gain=3.57869 0.530171 0.0041105 6.03024e-05 2.00948e-05 1.40241e-05
967
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 0.071279139270771077
968
+ decision_type=2 2 2 2 2 2
969
+ left_child=1 2 -1 4 -2 -4
970
+ right_child=3 -3 5 -5 -6 -7
971
+ leaf_value=-0.057523228626730809 0.052434699258117924 0.028193853840167954 -0.052225876034957666 0.05298703689463944 0.052031718022954213 -0.052688885577054048
972
+ leaf_weight=0.52994184568524438 0.43332421034574498 0.22096543014049519 1.1010594852268694 0.24872540682554234 1.0812997855246065 0.1920730024576186
973
+ leaf_count=8 10 3 27 5 29 4
974
+ internal_value=7.46771e-05 -0.0449492 -0.0538145 0.0522655 0.052147 -0.0522946
975
+ internal_weight=3.80739 2.04404 1.82307 1.76335 1.51462 1.29313
976
+ internal_count=86 42 39 44 39 31
977
+ is_linear=0
978
+ shrinkage=0.05
979
+
980
+
981
+ Tree=51
982
+ num_leaves=7
983
+ num_cat=0
984
+ split_feature=2 9 8 7 9 4
985
+ split_gain=3.39448 0.452159 0.00255562 0.000279377 3.65874e-05 1.02775e-05
986
+ threshold=1.0000000180025095e-35 0.056251581878005573 0.48529411764705888 3478.8119047619052 0.071839745922519613 1.0000000180025095e-35
987
+ decision_type=2 2 2 2 2 2
988
+ left_child=1 -1 4 -3 -2 -5
989
+ right_child=2 3 -4 5 -6 -7
990
+ leaf_value=-0.024398645429509903 -0.052086122604661216 0.053789475943882448 -0.057966061097239188 0.051963639842756697 -0.05281445221092828 0.052234256160140505
991
+ leaf_weight=0.21526079624891314 1.2279919162392614 0.27869978547096241 0.22100444883108128 0.93428360298275925 0.20059854164719571 0.56183291971683502
992
+ leaf_count=2 32 5 3 26 4 14
993
+ internal_value=7.36736e-05 0.0440359 -0.0529625 0.052336 -0.0521884 0.0520653
994
+ internal_weight=3.63967 1.99008 1.64959 1.77482 1.42859 1.49612
995
+ internal_count=86 47 39 45 36 40
996
+ is_linear=0
997
+ shrinkage=0.05
998
+
999
+
1000
+ Tree=52
1001
+ num_leaves=7
1002
+ num_cat=0
1003
+ split_feature=7 9 1 7 9 4
1004
+ split_gain=3.22825 0.421536 0.0023962 0.000239484 3.1822e-05 8.79587e-06
1005
+ threshold=3112.1875000000005 0.056251581878005573 1.5000000000000002 3478.8119047619052 0.071839745922519613 1.0000000180025095e-35
1006
+ decision_type=2 2 2 2 2 2
1007
+ left_child=2 -2 4 -3 -1 -5
1008
+ right_child=1 3 -4 5 -6 -7
1009
+ leaf_value=-0.051974075039210477 -0.022611362856803291 0.05359471917210145 -0.05772082104697214 0.051864312884805641 -0.052669764277071332 0.052120741719884484
1010
+ leaf_weight=1.1669210381805899 0.21176189929246891 0.26604645699262608 0.21785303950309742 0.89037709310650803 0.19132617488503445 0.53556656464934349
1011
+ leaf_count=32 2 5 3 26 4 14
1012
+ internal_value=7.52849e-05 0.043894 -0.0528529 0.0522176 -0.0520721 0.0519606
1013
+ internal_weight=3.47985 1.90375 1.5761 1.69199 1.35825 1.42594
1014
+ internal_count=86 47 39 45 36 40
1015
+ is_linear=0
1016
+ shrinkage=0.05
1017
+
1018
+
1019
+ Tree=53
1020
+ num_leaves=7
1021
+ num_cat=0
1022
+ split_feature=7 9 12 8 0 9
1023
+ split_gain=3.07242 0.457043 0.00292621 6.85316e-05 1.26423e-05 8.79499e-06
1024
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 0.071279139270771077
1025
+ decision_type=2 2 2 2 2 2
1026
+ left_child=1 2 -1 4 -2 -4
1027
+ right_child=3 -3 5 -5 -6 -7
1028
+ leaf_value=-0.056653332418161166 0.052081422039285598 0.026886719870256912 -0.051902369512977067 0.052781343332667423 0.051737953138033879 -0.052296246369048532
1029
+ leaf_weight=0.47251412272453341 0.37544408440589883 0.19973117113113392 0.95273867994546868 0.22473639249801625 0.93540499731898308 0.16649506613612175
1030
+ leaf_count=8 10 3 27 5 29 4
1031
+ internal_value=7.68379e-05 -0.0444079 -0.0533539 0.0519746 0.0518363 -0.051961
1032
+ internal_weight=3.32706 1.79148 1.59175 1.53559 1.31085 1.11923
1033
+ internal_count=86 42 39 44 39 31
1034
+ is_linear=0
1035
+ shrinkage=0.05
1036
+
1037
+
1038
+ Tree=54
1039
+ num_leaves=7
1040
+ num_cat=0
1041
+ split_feature=2 9 8 7 9 4
1042
+ split_gain=2.91014 0.383712 0.00181181 0.000198127 2.92829e-05 6.46712e-06
1043
+ threshold=1.0000000180025095e-35 0.056251581878005573 0.48529411764705888 3478.8119047619052 0.071839745922519613 1.0000000180025095e-35
1044
+ decision_type=2 2 2 2 2 2
1045
+ left_child=1 -1 4 -3 -2 -5
1046
+ right_child=2 3 -4 5 -6 -7
1047
+ leaf_value=-0.021271802133954222 -0.051783011187517117 0.053316114718123579 -0.05703372297539077 0.05168061391869886 -0.052476662686324463 0.051911335109327328
1048
+ leaf_weight=0.20203236490488086 1.0617324132472274 0.24565996043384064 0.19795862212777127 0.80830621160566785 0.17760059237480152 0.48653935268521309
1049
+ leaf_count=2 32 5 3 26 4 14
1050
+ internal_value=7.57291e-05 0.0435174 -0.0525919 0.0520143 -0.0518824 0.0517673
1051
+ internal_weight=3.17983 1.74254 1.43729 1.54051 1.23933 1.29485
1052
+ internal_count=86 47 39 45 36 40
1053
+ is_linear=0
1054
+ shrinkage=0.05
1055
+
1056
+
1057
+ Tree=55
1058
+ num_leaves=7
1059
+ num_cat=0
1060
+ split_feature=7 9 12 8 0 9
1061
+ split_gain=2.77243 0.415122 0.00229827 6.72112e-05 9.28698e-06 6.44362e-06
1062
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 0.071279139270771077
1063
+ decision_type=2 2 2 2 2 2
1064
+ left_child=1 2 -1 4 -2 -4
1065
+ right_child=3 -3 5 -5 -6 -7
1066
+ leaf_value=-0.056105784181477929 0.051875912022843002 0.025728307039962841 -0.051714463691019945 0.052628220802817885 0.051566996297964268 -0.052068182466303015
1067
+ leaf_weight=0.43628968298435244 0.34102963283658005 0.18887379392981518 0.86484081856906392 0.20874816179275502 0.84894894994795322 0.15127234905958176
1068
+ leaf_count=8 10 3 27 5 29 4
1069
+ internal_value=7.72327e-05 -0.0440025 -0.0530704 0.0518007 0.0516555 -0.0517671
1070
+ internal_weight=3.04 1.64128 1.4524 1.39873 1.18998 1.01611
1071
+ internal_count=86 42 39 44 39 31
1072
+ is_linear=0
1073
+ shrinkage=0.05
1074
+
1075
+
1076
+ Tree=56
1077
+ num_leaves=7
1078
+ num_cat=0
1079
+ split_feature=7 9 1 7 9 4
1080
+ split_gain=2.63473 0.341572 0.00145742 0.000164497 2.79808e-05 4.75855e-06
1081
+ threshold=3112.1875000000005 0.056251581878005573 1.5000000000000002 3478.8119047619052 0.071839745922519613 1.0000000180025095e-35
1082
+ decision_type=2 2 2 2 2 2
1083
+ left_child=2 -2 4 -3 -1 -5
1084
+ right_child=1 3 -4 5 -6 -7
1085
+ leaf_value=-0.051601775945499119 -0.019490915067937303 0.053064119977449434 -0.056470937512833552 0.051515543893373608 -0.052306738587078586 0.051723253421678043
1086
+ leaf_weight=0.96027212589979194 0.19069733470678318 0.22689381800591935 0.18708896823227394 0.73355109617114045 0.16493189707398415 0.4418221190571785
1087
+ leaf_count=32 2 5 3 26 4 14
1088
+ internal_value=7.6045e-05 0.0432934 -0.0523846 0.0518316 -0.0517051 0.0515936
1089
+ internal_weight=2.90526 1.59296 1.31229 1.40227 1.1252 1.17537
1090
+ internal_count=86 47 39 45 36 40
1091
+ is_linear=0
1092
+ shrinkage=0.05
1093
+
1094
+
1095
+ Tree=57
1096
+ num_leaves=7
1097
+ num_cat=0
1098
+ split_feature=7 9 12 8 0 9
1099
+ split_gain=2.50296 0.377471 0.00180685 6.5507e-05 6.82734e-06 4.72454e-06
1100
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 0.071279139270771077
1101
+ decision_type=2 2 2 2 2 2
1102
+ left_child=1 2 -1 4 -2 -4
1103
+ right_child=3 -3 5 -5 -6 -7
1104
+ leaf_value=-0.055607499936801578 0.051691338019982526 0.024607570301387913 -0.051545654679950564 0.052491083545236787 0.051413343360804969 -0.051863502878320505
1105
+ leaf_weight=0.40267548523843311 0.3096493426710365 0.17867011204361904 0.7847888395190239 0.19394304789602745 0.77025091089308262 0.13737773895263672
1106
+ leaf_count=8 10 3 27 5 29 4
1107
+ internal_value=7.74691e-05 -0.0436129 -0.0528132 0.051645 0.0514931 -0.051593
1108
+ internal_weight=2.77736 1.50351 1.32484 1.27384 1.0799 0.922167
1109
+ internal_count=86 42 39 44 39 31
1110
+ is_linear=0
1111
+ shrinkage=0.05
1112
+
1113
+
1114
+ Tree=58
1115
+ num_leaves=7
1116
+ num_cat=0
1117
+ split_feature=2 6 8 7 10 9
1118
+ split_gain=2.37829 0.331908 0.0011062 0.00011017 0.000136782 2.62188e-05
1119
+ threshold=1.0000000180025095e-35 34778.000000000007 0.48529411764705888 3723.3125000000005 1.0000000180025095e-35 0.071839745922519613
1120
+ decision_type=2 2 2 2 2 2
1121
+ left_child=1 -1 5 4 -3 -2
1122
+ right_child=2 3 -4 -5 -6 -7
1123
+ leaf_value=-0.02156846862497196 -0.051447960970868081 0.053214963622713496 -0.055910400839676348 0.051425765870829365 0.051493380966299018 -0.052157042905489059
1124
+ leaf_weight=0.17535035684704814 0.87325152195990063 0.24104415997862794 0.16981088370084751 0.82012460194528103 0.22129907086491585 0.15324152633547783
1125
+ leaf_count=3 32 5 3 31 8 4
1126
+ internal_value=7.62064e-05 0.0429519 -0.0521722 0.0517737 0.0523909 -0.0515538
1127
+ internal_weight=2.65412 1.45782 1.1963 1.28247 0.462343 1.02649
1128
+ internal_count=86 47 39 44 13 36
1129
+ is_linear=0
1130
+ shrinkage=0.05
1131
+
1132
+
1133
+ Tree=59
1134
+ num_leaves=7
1135
+ num_cat=0
1136
+ split_feature=7 6 1 7 10 9
1137
+ split_gain=2.26299 0.31039 0.00103136 9.44642e-05 0.000117133 2.27371e-05
1138
+ threshold=3112.1875000000005 34778.000000000007 1.5000000000000002 3723.3125000000005 1.0000000180025095e-35 0.071839745922519613
1139
+ decision_type=2 2 2 2 2 2
1140
+ left_child=2 -2 5 4 -3 -1
1141
+ right_child=1 3 -4 -5 -6 -7
1142
+ leaf_value=-0.051371228485124071 -0.019963441560389303 0.053050195574039838 -0.055724017708956963 0.051354380500580665 0.051418509974003837 -0.052047763416771343
1143
+ leaf_weight=0.82932015880942367 0.17249920591711987 0.22998244687914826 0.16708584688603878 0.78118370473384857 0.21080497466027737 0.14606617018580437
1144
+ leaf_count=32 3 5 3 31 8 4
1145
+ internal_value=7.76964e-05 0.0428216 -0.0520943 0.0516846 0.0522698 -0.0514725
1146
+ internal_weight=2.53694 1.39447 1.14247 1.22197 0.440787 0.975386
1147
+ internal_count=86 47 39 44 13 36
1148
+ is_linear=0
1149
+ shrinkage=0.05
1150
+
1151
+
1152
+ Tree=60
1153
+ num_leaves=7
1154
+ num_cat=0
1155
+ split_feature=7 9 12 8 0 1
1156
+ split_gain=2.15431 0.326455 0.0012912 3.99674e-05 6.38946e-06 3.60815e-06
1157
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 2.5000000000000004
1158
+ decision_type=2 2 2 2 2 2
1159
+ left_child=1 2 -1 4 5 -2
1160
+ right_child=3 -3 -4 -5 -6 -7
1161
+ leaf_value=-0.054978886079340053 0.051694326374548474 0.023438303528843456 -0.051364201244319722 0.052132262398176032 0.05121131365199786 0.051331171854148955
1162
+ leaf_weight=0.3580956254154446 0.1262961048632858 0.16136743128299702 0.79672552831470966 0.16806215979158878 0.66531687788665295 0.14919918403029442
1163
+ leaf_count=8 4 3 31 5 29 6
1164
+ internal_value=7.91249e-05 -0.0431767 -0.0524851 0.051422 0.0512952 0.0514977
1165
+ internal_weight=2.42506 1.31619 1.15482 1.10887 0.940812 0.275495
1166
+ internal_count=86 42 39 44 39 10
1167
+ is_linear=0
1168
+ shrinkage=0.05
1169
+
1170
+
1171
+ Tree=61
1172
+ num_leaves=7
1173
+ num_cat=0
1174
+ split_feature=2 6 8 7 10 9
1175
+ split_gain=2.04469 0.282176 0.000786681 7.58747e-05 9.33969e-05 2.14542e-05
1176
+ threshold=1.0000000180025095e-35 34778.000000000007 0.48529411764705888 3723.3125000000005 1.0000000180025095e-35 0.071839745922519613
1177
+ decision_type=2 2 2 2 2 2
1178
+ left_child=1 -1 5 4 -3 -2
1179
+ right_child=2 3 -4 -5 -6 -7
1180
+ leaf_value=-0.018754410714250033 -0.051240132484338098 0.052803334457989619 -0.055240518558143659 0.05122231258980349 0.051279791758668064 -0.051922877177181609
1181
+ leaf_weight=0.1638234127312902 0.753892747685313 0.21225182898342587 0.15163859352469444 0.70860414579510689 0.19121293351054192 0.13578741624951363
1182
+ leaf_count=3 32 5 3 31 8 4
1183
+ internal_value=7.77931e-05 0.042509 -0.0519117 0.051534 0.0520813 -0.0513443
1184
+ internal_weight=2.31721 1.27589 1.04132 1.11207 0.403465 0.88968
1185
+ internal_count=86 47 39 44 13 36
1186
+ is_linear=0
1187
+ shrinkage=0.05
1188
+
1189
+
1190
+ Tree=62
1191
+ num_leaves=7
1192
+ num_cat=0
1193
+ split_feature=7 9 12 8 0 1
1194
+ split_gain=1.94651 0.297435 0.00101562 2.88044e-05 5.81162e-06 3.07927e-06
1195
+ threshold=3449.4166666666674 0.075080247396852748 0.050737547542749055 0.40833333333333338 3.5000000000000004 2.5000000000000004
1196
+ decision_type=2 2 2 2 2 2
1197
+ left_child=1 2 -1 4 5 -2
1198
+ right_child=3 -3 -4 -5 -6 -7
1199
+ leaf_value=-0.054578448834940924 0.051567146719777759 0.02239266513495286 -0.051230840124425414 0.051923259839556216 0.051093357651703752 0.051217690768354543
1200
+ leaf_weight=0.33006986044347308 0.11681191995739915 0.15269735455512989 0.72255352139472961 0.15266721323132515 0.60328011028468609 0.13693635538220406
1201
+ leaf_count=8 4 3 31 5 29 6
1202
+ internal_value=7.91791e-05 -0.0428205 -0.0522805 0.0512905 0.0511778 0.0513786
1203
+ internal_weight=2.21502 1.20532 1.05262 1.0097 0.857028 0.253748
1204
+ internal_count=86 42 39 44 39 10
1205
+ is_linear=0
1206
+ shrinkage=0.05
1207
+
1208
+
1209
+ Tree=63
1210
+ num_leaves=7
1211
+ num_cat=0
1212
+ split_feature=7 3 1 7 8 9
1213
+ split_gain=1.85432 0.417261 0.000627643 7.49571e-05 2.58057e-05 2.06953e-05
1214
+ threshold=3112.1875000000005 0.00030309239974547856 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1215
+ decision_type=2 2 2 2 2 2
1216
+ left_child=2 -2 5 -3 -5 -1
1217
+ right_child=1 3 -4 4 -6 -7
1218
+ leaf_value=-0.051115534100290363 -0.05621703388787315 0.052345240117811768 -0.054820674551753504 0.051099128658674879 0.051827121348642452 -0.051812371493738234
1219
+ leaf_weight=0.68126739375293288 0.098359771072864421 0.17671952582895734 0.14278436452150345 0.74558193981647491 0.14548412896692753 0.12630220502614975
1220
+ leaf_count=32 1 5 3 36 5 4
1221
+ internal_value=7.77907e-05 0.0423271 -0.0517648 0.0514045 0.051218 -0.0512245
1222
+ internal_weight=2.1165 1.16615 0.950354 1.06779 0.891066 0.80757
1223
+ internal_count=86 47 39 46 41 36
1224
+ is_linear=0
1225
+ shrinkage=0.05
1226
+
1227
+
1228
+ Tree=64
1229
+ num_leaves=7
1230
+ num_cat=0
1231
+ split_feature=7 3 1 7 8 9
1232
+ split_gain=1.76081 0.396075 0.000539014 6.43369e-05 2.21824e-05 1.77622e-05
1233
+ threshold=3112.1875000000005 0.00030309239974547856 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1234
+ decision_type=2 2 2 2 2 2
1235
+ left_child=2 -2 5 -3 -5 -1
1236
+ right_child=1 3 -4 4 -6 -7
1237
+ leaf_value=-0.051059950058782694 -0.05587717407369152 0.052227077895438845 -0.054573725316594573 0.051044416503153757 0.05173590102714111 -0.05172133134698214
1238
+ leaf_weight=0.64872924610972438 0.094117365777492412 0.16847153194248654 0.13641974702477455 0.70996048487722874 0.13862663134932518 0.12034851312637329
1239
+ leaf_count=32 1 5 3 36 5 4
1240
+ internal_value=7.81751e-05 0.0422537 -0.0516772 0.0513346 0.0511574 -0.0511634
1241
+ internal_weight=2.01667 1.11118 0.905498 1.01706 0.848587 0.769078
1242
+ internal_count=86 47 39 46 41 36
1243
+ is_linear=0
1244
+ shrinkage=0.05
1245
+
1246
+
1247
+ Tree=65
1248
+ num_leaves=7
1249
+ num_cat=0
1250
+ split_feature=7 1 1 7 8 9
1251
+ split_gain=1.67215 0.376009 0.000462941 5.52291e-05 1.90695e-05 1.52469e-05
1252
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1253
+ decision_type=2 2 2 2 2 2
1254
+ left_child=2 -2 5 -3 -5 -1
1255
+ right_child=1 3 -4 4 -6 -7
1256
+ leaf_value=-0.051007192846026506 -0.055557784036650598 0.052115048839742974 -0.054339948385641892 0.05099247923509416 0.051649338939514816 -0.051634989287311799
1257
+ leaf_weight=0.6177124995738269 0.090028844773769157 0.16059087030589581 0.13031063787639141 0.67600579001009464 0.1320811565965414 0.11466564238071442
1258
+ leaf_count=32 1 5 3 36 5 4
1259
+ internal_value=7.85369e-05 0.042184 -0.0515941 0.0512681 0.0510998 -0.0511055
1260
+ internal_weight=1.9214 1.05871 0.862689 0.968678 0.808087 0.732378
1261
+ internal_count=86 47 39 46 41 36
1262
+ is_linear=0
1263
+ shrinkage=0.05
1264
+
1265
+
1266
+ Tree=66
1267
+ num_leaves=7
1268
+ num_cat=0
1269
+ split_feature=7 1 1 7 8 9
1270
+ split_gain=1.58809 0.357 0.000397637 4.7417e-05 1.63942e-05 1.30896e-05
1271
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1272
+ decision_type=2 2 2 2 2 2
1273
+ left_child=2 -2 5 -3 -5 -1
1274
+ right_child=1 3 -4 4 -6 -7
1275
+ leaf_value=-0.050957112841780151 -0.055257426518026911 0.052008818494963222 -0.054118591481353243 0.050943172734727149 0.051567181814864339 -0.051553093612409742
1276
+ leaf_weight=0.58814921975135814 0.086091794073581474 0.15306290239095688 0.12444949708878994 0.64364327490329742 0.12583459727466106 0.10924223065376282
1277
+ leaf_count=32 1 5 3 36 5 4
1278
+ internal_value=7.88775e-05 0.042118 -0.0515151 0.0512051 0.0510452 -0.0510505
1279
+ internal_weight=1.83047 1.00863 0.821841 0.922541 0.769478 0.697391
1280
+ internal_count=86 47 39 46 41 36
1281
+ is_linear=0
1282
+ shrinkage=0.05
1283
+
1284
+
1285
+ Tree=67
1286
+ num_leaves=7
1287
+ num_cat=0
1288
+ split_feature=7 1 1 7 8 9
1289
+ split_gain=1.50838 0.338986 0.000341575 4.07151e-05 1.40952e-05 1.1239e-05
1290
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1291
+ decision_type=2 2 2 2 2 2
1292
+ left_child=2 -2 5 -3 -5 -1
1293
+ right_child=1 3 -4 4 -6 -7
1294
+ leaf_value=-0.05090956693516191 -0.054974794454426261 0.051908070683690404 -0.053908957765295529 0.050896358129884182 0.051489200516835226 -0.051475401949002943
1295
+ leaf_weight=0.55997416283935308 0.082303412258625031 0.14587339106947184 0.11882872879505157 0.61280137114226818 0.11987428180873394 0.10406730137765408
1296
+ leaf_count=32 1 5 3 36 5 4
1297
+ internal_value=7.91989e-05 0.0420553 -0.05144 0.0511452 0.0509934 -0.0509982
1298
+ internal_weight=1.74372 0.960852 0.78287 0.878549 0.732676 0.664041
1299
+ internal_count=86 47 39 46 41 36
1300
+ is_linear=0
1301
+ shrinkage=0.05
1302
+
1303
+
1304
+ Tree=68
1305
+ num_leaves=7
1306
+ num_cat=0
1307
+ split_feature=7 1 1 7 8 9
1308
+ split_gain=1.43277 0.321913 0.00029344 3.49645e-05 1.21194e-05 9.6512e-06
1309
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1310
+ decision_type=2 2 2 2 2 2
1311
+ left_child=2 -2 5 -3 -5 -1
1312
+ right_child=1 3 -4 4 -6 -7
1313
+ leaf_value=-0.050864425030284301 -0.054708686955405844 0.051812507153240289 -0.053710385607623638 0.050851905653572706 0.051415173768754034 -0.051401690927245863
1314
+ leaf_weight=0.53312471695244312 0.078660592436790466 0.13900853041559458 0.11344072036445141 0.58341139741241932 0.11418797634541988 0.099130252376198769
1315
+ leaf_count=32 1 5 3 36 5 4
1316
+ internal_value=7.95015e-05 0.0419959 -0.0513688 0.0510884 0.0509441 -0.0509487
1317
+ internal_weight=1.66096 0.915268 0.745696 0.836608 0.697599 0.632255
1318
+ internal_count=86 47 39 46 41 36
1319
+ is_linear=0
1320
+ shrinkage=0.05
1321
+
1322
+
1323
+ Tree=69
1324
+ num_leaves=7
1325
+ num_cat=0
1326
+ split_feature=7 1 1 7 8 9
1327
+ split_gain=1.36104 0.305727 0.000252108 3.00296e-05 1.0421e-05 8.28868e-06
1328
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1329
+ decision_type=2 2 2 2 2 2
1330
+ left_child=2 -2 5 -3 -5 -1
1331
+ right_child=1 3 -4 4 -6 -7
1332
+ leaf_value=-0.050821559664680674 -0.054457997003113136 0.051721850934761386 -0.05352225117720219 0.050809692856807032 0.051344893548052079 -0.051331744608704925
1333
+ leaf_weight=0.50754085648804903 0.075159996747970581 0.13245491962879896 0.10827787034213543 0.55540751945227385 0.10876388847827911 0.094420861452817917
1334
+ leaf_count=32 1 5 3 36 5 4
1335
+ internal_value=7.97873e-05 0.0419395 -0.0513011 0.0510344 0.0508973 -0.0509016
1336
+ internal_weight=1.58203 0.871786 0.71024 0.796626 0.664171 0.601962
1337
+ internal_count=86 47 39 46 41 36
1338
+ is_linear=0
1339
+ shrinkage=0.05
1340
+
1341
+
1342
+ Tree=70
1343
+ num_leaves=7
1344
+ num_cat=0
1345
+ split_feature=7 1 1 7 8 9
1346
+ split_gain=1.293 0.29038 0.000216615 2.5794e-05 8.96111e-06 7.11943e-06
1347
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1348
+ decision_type=2 2 2 2 2 2
1349
+ left_child=2 -2 5 -3 -5 -1
1350
+ right_child=1 3 -4 4 -6 -7
1351
+ leaf_value=-0.050780852112463595 -0.054221723843112357 0.051635834443303885 -0.053343981189505944 0.050769602713120303 0.051278163205708376 -0.051265367414903423
1352
+ leaf_weight=0.48316505178809166 0.071798063814640045 0.12619960773736238 0.10333260335028172 0.52872663736343384 0.10359065048396587 0.089929260313510895
1353
+ leaf_count=32 1 5 3 36 5 4
1354
+ internal_value=8.00567e-05 0.041886 -0.0512368 0.0509832 0.0508529 -0.0508569
1355
+ internal_weight=1.50674 0.830315 0.676427 0.758517 0.632317 0.573094
1356
+ internal_count=86 47 39 46 41 36
1357
+ is_linear=0
1358
+ shrinkage=0.05
1359
+
1360
+
1361
+ Tree=71
1362
+ num_leaves=7
1363
+ num_cat=0
1364
+ split_feature=7 1 1 7 8 9
1365
+ split_gain=1.22843 0.275826 0.000186132 2.21579e-05 7.70615e-06 6.11564e-06
1366
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1367
+ decision_type=2 2 2 2 2 2
1368
+ left_child=2 -2 5 -3 -5 -1
1369
+ right_child=1 3 -4 4 -6 -7
1370
+ leaf_value=-0.050742192952038773 -0.053998904107683078 0.051554210494796596 -0.053175019475394159 0.050731526188011147 0.051214799713511339 -0.051202363106691677
1371
+ leaf_weight=0.45994213689118624 0.068571113049983978 0.12023004423826933 0.098597445525228977 0.50330829620361328 0.098657312802970409 0.085645964369177818
1372
+ leaf_count=32 1 5 3 36 5 4
1373
+ internal_value=8.03114e-05 0.0418352 -0.0511757 0.0509345 0.0508107 -0.0508144
1374
+ internal_weight=1.43495 0.790767 0.644186 0.722196 0.601966 0.545588
1375
+ internal_count=86 47 39 46 41 36
1376
+ is_linear=0
1377
+ shrinkage=0.05
1378
+
1379
+
1380
+ Tree=72
1381
+ num_leaves=7
1382
+ num_cat=0
1383
+ split_feature=7 1 1 7 8 9
1384
+ split_gain=1.16716 0.262021 0.00015995 1.90365e-05 6.62721e-06 5.25387e-06
1385
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1386
+ decision_type=2 2 2 2 2 2
1387
+ left_child=2 -2 5 -3 -5 -1
1388
+ right_child=1 3 -4 4 -6 -7
1389
+ leaf_value=-0.050705475390719956 -0.053788693608861321 0.051476751584957708 -0.053014865889454477 0.050695358214698165 0.051154623874076044 -0.051142556259255872
1390
+ leaf_weight=0.43781932163983583 0.065475329756736755 0.1145341070368886 0.094064968638122082 0.4790946114808321 0.093953348696231842 0.081561826169490814
1391
+ leaf_count=32 1 5 3 36 5 4
1392
+ internal_value=8.05513e-05 0.041787 -0.0511177 0.0508883 0.0507707 -0.0507741
1393
+ internal_weight=1.3665 0.753057 0.613446 0.687582 0.573048 0.519381
1394
+ internal_count=86 47 39 46 41 36
1395
+ is_linear=0
1396
+ shrinkage=0.05
1397
+
1398
+
1399
+ Tree=73
1400
+ num_leaves=7
1401
+ num_cat=0
1402
+ split_feature=7 1 1 7 8 9
1403
+ split_gain=1.10901 0.248925 0.000137461 1.63561e-05 5.69959e-06 4.51411e-06
1404
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1405
+ decision_type=2 2 2 2 2 2
1406
+ left_child=2 -2 5 -3 -5 -1
1407
+ right_child=1 3 -4 4 -6 -7
1408
+ leaf_value=-0.050670596929096236 -0.053590293940464412 0.051403226371230605 -0.052863026678438688 0.050661002316579024 0.05109747441563757 -0.051085784812353235
1409
+ leaf_weight=0.41674605384469032 0.062506809830665588 0.10910011269152164 0.089727881364524364 0.45603015180677176 0.089468623511493206 0.077668054029345512
1410
+ leaf_count=32 1 5 3 36 5 4
1411
+ internal_value=8.07783e-05 0.0417413 -0.0510626 0.0508444 0.0507326 -0.0507358
1412
+ internal_weight=1.30125 0.717106 0.584142 0.654599 0.545499 0.494414
1413
+ internal_count=86 47 39 46 41 36
1414
+ is_linear=0
1415
+ shrinkage=0.05
1416
+
1417
+
1418
+ Tree=74
1419
+ num_leaves=7
1420
+ num_cat=0
1421
+ split_feature=7 1 1 7 8 9
1422
+ split_gain=1.05381 0.2365 0.000118141 1.40545e-05 4.90203e-06 3.87871e-06
1423
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1424
+ decision_type=2 2 2 2 2 2
1425
+ left_child=2 -2 5 -3 -5 -1
1426
+ right_child=1 3 -4 4 -6 -7
1427
+ leaf_value=-0.050637467274374216 -0.053402953026905391 0.051333436912432265 -0.052719051611408531 0.050628363751426902 0.051043193153155614 -0.051031878578930726
1428
+ leaf_weight=0.39667393453419209 0.059661626815795898 0.10391676984727383 0.085579007863998413 0.43406189233064651 0.085193407721817493 0.073956210166215897
1429
+ leaf_count=32 1 5 3 36 5 4
1430
+ internal_value=8.09923e-05 0.0416978 -0.0510102 0.0508026 0.0506964 -0.0506994
1431
+ internal_weight=1.23904 0.682834 0.556209 0.623172 0.519255 0.47063
1432
+ internal_count=86 47 39 46 41 36
1433
+ is_linear=0
1434
+ shrinkage=0.05
1435
+
1436
+
1437
+ Tree=75
1438
+ num_leaves=7
1439
+ num_cat=0
1440
+ split_feature=7 1 1 7 8 9
1441
+ split_gain=1.00141 0.224708 0.000101543 1.20777e-05 4.21624e-06 3.3331e-06
1442
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1443
+ decision_type=2 2 2 2 2 2
1444
+ left_child=2 -2 5 -3 -5 -1
1445
+ right_child=1 3 -4 4 -6 -7
1446
+ leaf_value=-0.050605992909838506 -0.053225989443578751 0.051267180988710331 -0.052582510474784787 0.050597355899224455 0.050991633012501328 -0.050980696151185459
1447
+ leaf_weight=0.37755667231976986 0.056935809552669525 0.098973212763667107 0.081611312925815582 0.4131390955299139 0.081118350848555565 0.070418167859315872
1448
+ leaf_count=32 1 5 3 36 5 4
1449
+ internal_value=8.11955e-05 0.0416566 -0.0509604 0.050763 0.0506621 -0.0506649
1450
+ internal_weight=1.17975 0.650166 0.529586 0.593231 0.494257 0.447975
1451
+ internal_count=86 47 39 46 41 36
1452
+ is_linear=0
1453
+ shrinkage=0.05
1454
+
1455
+
1456
+ Tree=76
1457
+ num_leaves=7
1458
+ num_cat=0
1459
+ split_feature=7 1 1 7 8 9
1460
+ split_gain=0.951663 0.213518 8.72826e-05 1.03797e-05 3.62654e-06 2.86445e-06
1461
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1462
+ decision_type=2 2 2 2 2 2
1463
+ left_child=2 -2 5 -3 -5 -1
1464
+ right_child=1 3 -4 4 -6 -7
1465
+ leaf_value=-0.050576091499889057 -0.053058772421869627 0.051204274601570433 -0.052453005695592851 0.050567895908851794 0.050942655295936667 -0.050932092913472754
1466
+ leaf_weight=0.35934999864548445 0.054325386881828308 0.09425897803157568 0.077817914076149464 0.39321324229240417 0.077234478667378426 0.067046143114566803
1467
+ leaf_count=32 1 5 3 36 5 4
1468
+ internal_value=8.13871e-05 0.0416174 -0.0509131 0.0507254 0.0506294 -0.0506321
1469
+ internal_weight=1.12325 0.619032 0.504214 0.564707 0.470448 0.426396
1470
+ internal_count=86 47 39 46 41 36
1471
+ is_linear=0
1472
+ shrinkage=0.05
1473
+
1474
+
1475
+ Tree=77
1476
+ num_leaves=7
1477
+ num_cat=0
1478
+ split_feature=7 1 1 7 8 9
1479
+ split_gain=0.90443 0.202896 7.50295e-05 8.92117e-06 3.11936e-06 2.46191e-06
1480
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1481
+ decision_type=2 2 2 2 2 2
1482
+ left_child=2 -2 5 -3 -5 -1
1483
+ right_child=1 3 -4 4 -6 -7
1484
+ leaf_value=-0.050547681250523127 -0.052900709212720103 0.051144543684963434 -0.052330160079570358 0.050539903777803798 0.050896121373446214 -0.050885935733301926
1485
+ leaf_weight=0.34201156906783581 0.051826409995555878 0.089763985946774483 0.074192073196172714 0.37423794716596603 0.073533183895051479 0.063832654617726803
1486
+ leaf_count=32 1 5 3 36 5 4
1487
+ internal_value=8.15681e-05 0.0415802 -0.0508682 0.0506896 0.0505984 -0.0506009
1488
+ internal_weight=1.0694 0.589362 0.480036 0.537535 0.447771 0.405844
1489
+ internal_count=86 47 39 46 41 36
1490
+ is_linear=0
1491
+ shrinkage=0.05
1492
+
1493
+
1494
+ Tree=78
1495
+ num_leaves=7
1496
+ num_cat=0
1497
+ split_feature=7 1 1 7 8 9
1498
+ split_gain=0.85958 0.192814 6.44998e-05 7.66809e-06 2.68325e-06 2.1161e-06
1499
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1500
+ decision_type=2 2 2 2 2 2
1501
+ left_child=2 -2 5 -3 -5 -1
1502
+ right_child=1 3 -4 4 -6 -7
1503
+ leaf_value=-0.050520685962263473 -0.052751246317376467 0.051087821225232033 -0.052213607565704284 0.050513304914895744 0.05085191371230572 -0.050842097782830324
1504
+ leaf_weight=0.3255009176209569 0.049434956163167953 0.085478556342422962 0.070727241225540638 0.35616886988282204 0.070006201975047588 0.060770533978939056
1505
+ leaf_count=32 1 5 3 36 5 4
1506
+ internal_value=8.17398e-05 0.0415449 -0.0508254 0.0506556 0.0505689 -0.0505713
1507
+ internal_weight=1.01809 0.561089 0.456999 0.511654 0.426175 0.386271
1508
+ internal_count=86 47 39 46 41 36
1509
+ is_linear=0
1510
+ shrinkage=0.05
1511
+
1512
+
1513
+ Tree=79
1514
+ num_leaves=7
1515
+ num_cat=0
1516
+ split_feature=7 1 1 7 8 9
1517
+ split_gain=0.816989 0.183241 5.54509e-05 6.59146e-06 2.30818e-06 1.81894e-06
1518
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1519
+ decision_type=2 2 2 2 2 2
1520
+ left_child=2 -2 5 -3 -5 -1
1521
+ right_child=1 3 -4 4 -6 -7
1522
+ leaf_value=-0.050495035577253246 -0.052609879592763309 0.051033952868473467 -0.052103016195310527 0.050488029243038579 0.050809911209010843 -0.050800455235265052
1523
+ leaf_weight=0.30977936740964651 0.047147151082754135 0.081393376924097538 0.067417027428746223 0.33896364271640778 0.06664562039077282 0.057852909900248051
1524
+ leaf_count=32 1 5 3 36 5 4
1525
+ internal_value=8.1902e-05 0.0415114 -0.0507848 0.0506233 0.0505409 -0.0505431
1526
+ internal_weight=0.969199 0.53415 0.435049 0.487003 0.405609 0.367632
1527
+ internal_count=86 47 39 46 41 36
1528
+ is_linear=0
1529
+ shrinkage=0.05
1530
+
1531
+
1532
+ Tree=80
1533
+ num_leaves=7
1534
+ num_cat=0
1535
+ split_feature=7 1 1 7 8 9
1536
+ split_gain=0.776539 0.174152 4.76742e-05 5.66639e-06 1.9856e-06 1.56364e-06
1537
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1538
+ decision_type=2 2 2 2 2 2
1539
+ left_child=2 -2 5 -3 -5 -1
1540
+ right_child=1 3 -4 4 -6 -7
1541
+ leaf_value=-0.050470660707490289 -0.052476120132517505 0.050982791862173887 -0.051998074717629184 0.050464009597148043 0.050770000967523543 -0.050760901592487029
1542
+ leaf_weight=0.29480996914207935 0.044959183782339096 0.0774995107203722 0.064255218952894211 0.32258179038763046 0.063443851657211781 0.055073190480470657
1543
+ leaf_count=32 1 5 3 36 5 4
1544
+ internal_value=8.20557e-05 0.0414795 -0.0507462 0.0505926 0.0505143 -0.0505163
1545
+ internal_weight=0.922623 0.508484 0.414138 0.463525 0.386026 0.349883
1546
+ internal_count=86 47 39 46 41 36
1547
+ is_linear=0
1548
+ shrinkage=0.05
1549
+
1550
+
1551
+ Tree=81
1552
+ num_leaves=7
1553
+ num_cat=0
1554
+ split_feature=7 1 1 7 8 9
1555
+ split_gain=0.73812 0.165521 4.099e-05 4.87137e-06 1.70813e-06 1.34427e-06
1556
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1557
+ decision_type=2 2 2 2 2 2
1558
+ left_child=2 -2 5 -3 -5 -1
1559
+ right_child=1 3 -4 4 -6 -7
1560
+ leaf_value=-0.050447496670033211 -0.052349535760650268 0.050934193609556068 -0.051898474212720883 0.050441182498878724 0.050732075860885184 -0.050723328052498809
1561
+ leaf_weight=0.28055744618177414 0.042867299169301987 0.073788378853350878 0.061235793866217136 0.30698465742170811 0.060393636114895344 0.052425066940486431
1562
+ leaf_count=32 1 5 3 36 5 4
1563
+ internal_value=8.22008e-05 0.0414492 -0.0507096 0.0505635 0.050489 -0.0504909
1564
+ internal_weight=0.878252 0.484034 0.394218 0.441167 0.367378 0.332983
1565
+ internal_count=86 47 39 46 41 36
1566
+ is_linear=0
1567
+ shrinkage=0.05
1568
+
1569
+
1570
+ Tree=82
1571
+ num_leaves=7
1572
+ num_cat=0
1573
+ split_feature=7 1 1 7 8 9
1574
+ split_gain=0.701627 0.157325 3.52449e-05 4.18822e-06 1.46947e-06 1.15574e-06
1575
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1576
+ decision_type=2 2 2 2 2 2
1577
+ left_child=2 -2 5 -3 -5 -1
1578
+ right_child=1 3 -4 4 -6 -7
1579
+ leaf_value=-0.050425480302814775 -0.052229705204215097 0.05088803351331226 -0.051803939908119739 0.050419487462217007 0.050696035386165586 -0.050687628735126826
1580
+ leaf_weight=0.26698806835338473 0.040867827832698822 0.070251740515232086 0.058352903928607702 0.29213531780987978 0.057488014455884695 0.04990250151604414
1581
+ leaf_count=32 1 5 3 36 5 4
1582
+ internal_value=8.23391e-05 0.0414205 -0.0506747 0.0505357 0.050465 -0.0504668
1583
+ internal_weight=0.835986 0.460743 0.375243 0.419875 0.349623 0.316891
1584
+ internal_count=86 47 39 46 41 36
1585
+ is_linear=0
1586
+ shrinkage=0.05
1587
+
1588
+
1589
+ Tree=83
1590
+ num_leaves=7
1591
+ num_cat=0
1592
+ split_feature=7 1 1 7 8 9
1593
+ split_gain=0.666961 0.14954 3.03061e-05 3.60101e-06 1.26423e-06 9.93682e-07
1594
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1595
+ decision_type=2 2 2 2 2 2
1596
+ left_child=2 -2 5 -3 -5 -1
1597
+ right_child=1 3 -4 4 -6 -7
1598
+ leaf_value=-0.050404560437629559 -0.052116234595129485 0.050844179989428556 -0.051714197764098593 0.05039886753862708 0.050661788649821377 -0.050653713021414817
1599
+ leaf_weight=0.25406966172158718 0.038957182317972183 0.066881705075502396 0.055600891355425119 0.2779985386878252 0.054720326326787472 0.047499710693955421
1600
+ leaf_count=32 1 5 3 36 5 4
1601
+ internal_value=8.24693e-05 0.0413931 -0.0506416 0.0505094 0.0504421 -0.0504438
1602
+ internal_weight=0.795728 0.438558 0.35717 0.399601 0.332719 0.301569
1603
+ internal_count=86 47 39 46 41 36
1604
+ is_linear=0
1605
+ shrinkage=0.05
1606
+
1607
+
1608
+ Tree=84
1609
+ num_leaves=7
1610
+ num_cat=0
1611
+ split_feature=7 1 1 7 8 9
1612
+ split_gain=0.634029 0.142147 2.60608e-05 3.09632e-06 1.08764e-06 8.54411e-07
1613
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1614
+ decision_type=2 2 2 2 2 2
1615
+ left_child=2 -2 5 -3 -5 -1
1616
+ right_child=1 3 -4 4 -6 -7
1617
+ leaf_value=-0.050384673639876559 -0.052008767086894052 0.050802517799151063 -0.051629003038727722 0.050379268663426437 0.050629237053843035 -0.050621486875958716
1618
+ leaf_weight=0.2417715173214674 0.037131857126951218 0.063670704141259193 0.052974275313317776 0.26454066997393966 0.052084204275161028 0.045211158692836761
1619
+ leaf_count=32 1 5 3 36 5 4
1620
+ internal_value=8.25933e-05 0.0413672 -0.0506101 0.0504844 0.0504204 -0.050422
1621
+ internal_weight=0.757384 0.417427 0.339957 0.380296 0.316625 0.286983
1622
+ internal_count=86 47 39 46 41 36
1623
+ is_linear=0
1624
+ shrinkage=0.05
1625
+
1626
+
1627
+ Tree=85
1628
+ num_leaves=7
1629
+ num_cat=0
1630
+ split_feature=7 1 1 7 8 9
1631
+ split_gain=0.602742 0.135123 2.2411e-05 2.66251e-06 9.35745e-07 7.34683e-07
1632
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1633
+ decision_type=2 2 2 2 2 2
1634
+ left_child=2 -2 5 -3 -5 -1
1635
+ right_child=1 3 -4 4 -6 -7
1636
+ leaf_value=-0.050365774686833502 -0.051906961115474784 0.050762936756621184 -0.051548115528713426 0.050360639537289399 0.050598300838636423 -0.050590866292310327
1637
+ leaf_weight=0.23006430780515075 0.035388439893722534 0.060611481312662363 0.050467765424400568 0.25172960478812456 0.049573555588722229 0.043031559325754642
1638
+ leaf_count=32 1 5 3 36 5 4
1639
+ internal_value=8.27099e-05 0.0413425 -0.0505801 0.0504606 0.0503997 -0.0504012
1640
+ internal_weight=0.720867 0.397303 0.323564 0.361915 0.301303 0.273096
1641
+ internal_count=86 47 39 46 41 36
1642
+ is_linear=0
1643
+ shrinkage=0.05
1644
+
1645
+
1646
+ Tree=86
1647
+ num_leaves=7
1648
+ num_cat=0
1649
+ split_feature=7 1 1 7 8 9
1650
+ split_gain=0.573016 0.128451 1.9273e-05 2.28958e-06 8.05081e-07 6.31772e-07
1651
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1652
+ decision_type=2 2 2 2 2 2
1653
+ left_child=2 -2 5 -3 -5 -1
1654
+ right_child=1 3 -4 4 -6 -7
1655
+ leaf_value=-0.050347807744096633 -0.051810504678536623 0.050725328219376711 -0.051471306045197208 0.050342931810607429 0.050568897155633556 -0.050561767693004633
1656
+ leaf_weight=0.2189200627617538 0.033723615109920502 0.057697092182934284 0.048076252453029156 0.2395347086712718 0.047182553447782993 0.040955848060548306
1657
+ leaf_count=32 1 5 3 36 5 4
1658
+ internal_value=8.28212e-05 0.0413191 -0.0505517 0.0504379 0.0503801 -0.0503815
1659
+ internal_weight=0.68609 0.378138 0.307952 0.344414 0.286717 0.259876
1660
+ internal_count=86 47 39 46 41 36
1661
+ is_linear=0
1662
+ shrinkage=0.05
1663
+
1664
+
1665
+ Tree=87
1666
+ num_leaves=7
1667
+ num_cat=0
1668
+ split_feature=7 1 1 7 8 9
1669
+ split_gain=0.544771 0.122113 1.65751e-05 1.96897e-06 6.92684e-07 5.43303e-07
1670
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1671
+ decision_type=2 2 2 2 2 2
1672
+ left_child=2 -2 5 -3 -5 -1
1673
+ right_child=1 3 -4 4 -6 -7
1674
+ leaf_value=-0.050330730324470864 -0.051719086213005222 0.050689591046640982 -0.0513983691992854 0.050326098707277403 0.05054094975433629 -0.050534116756501969
1675
+ leaf_weight=0.20831208350136876 0.032134164124727249 0.054920890368521214 0.045794803649187088 0.22792675299569964 0.044905624818056822 0.038979186676442623
1676
+ leaf_count=32 1 5 3 36 5 4
1677
+ internal_value=8.29264e-05 0.0412968 -0.0505246 0.0504164 0.0503615 -0.0503628
1678
+ internal_weight=0.652974 0.359887 0.293086 0.327753 0.272832 0.247291
1679
+ internal_count=86 47 39 46 41 36
1680
+ is_linear=0
1681
+ shrinkage=0.05
1682
+
1683
+
1684
+ Tree=88
1685
+ num_leaves=7
1686
+ num_cat=0
1687
+ split_feature=7 1 1 7 8 9
1688
+ split_gain=0.517933 0.116091 1.42555e-05 1.69331e-06 5.95974e-07 4.67253e-07
1689
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1690
+ decision_type=2 2 2 2 2 2
1691
+ left_child=2 -2 5 -3 -5 -1
1692
+ right_child=1 3 -4 4 -6 -7
1693
+ leaf_value=-0.050314498253588726 -0.051632443098970984 0.050655631740285201 -0.051329106525360046 0.050310098101705926 0.050514383222273918 -0.050507842204426924
1694
+ leaf_weight=0.19821489881724119 0.030616963282227516 0.05227650748565793 0.04361866507679224 0.2168778539635241 0.042737449519336224 0.037096953019499779
1695
+ leaf_count=32 1 5 3 36 5 4
1696
+ internal_value=8.30259e-05 0.0412757 -0.0504989 0.050396 0.0503437 -0.050345
1697
+ internal_weight=0.621439 0.342509 0.278931 0.311892 0.259615 0.235312
1698
+ internal_count=86 47 39 46 41 36
1699
+ is_linear=0
1700
+ shrinkage=0.05
1701
+
1702
+
1703
+ Tree=89
1704
+ num_leaves=7
1705
+ num_cat=0
1706
+ split_feature=7 1 1 7 8 9
1707
+ split_gain=0.492429 0.110369 1.2261e-05 1.45631e-06 5.12793e-07 4.0185e-07
1708
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1709
+ decision_type=2 2 2 2 2 2
1710
+ left_child=2 -2 5 -3 -5 -1
1711
+ right_child=1 3 -4 4 -6 -7
1712
+ leaf_value=-0.050299064368726144 -0.051550292034122147 0.050623360312032475 -0.051263319891751882 0.05029488594998547 0.050489132277186634 -0.050482864286208989
1713
+ leaf_weight=0.18860423564910889 0.029168995097279549 0.049757851287722588 0.04154325881972909 0.20636141765862703 0.0406729388050735 0.03530473168939352
1714
+ leaf_count=32 1 5 3 36 5 4
1715
+ internal_value=8.31209e-05 0.0412555 -0.0504744 0.0503766 0.0503269 -0.050328
1716
+ internal_weight=0.591413 0.325961 0.265452 0.296792 0.247034 0.223909
1717
+ internal_count=86 47 39 46 41 36
1718
+ is_linear=0
1719
+ shrinkage=0.05
1720
+
1721
+
1722
+ Tree=90
1723
+ num_leaves=7
1724
+ num_cat=0
1725
+ split_feature=7 1 1 7 8 9
1726
+ split_gain=0.468192 0.104931 1.05458e-05 1.25255e-06 4.41224e-07 3.45617e-07
1727
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1728
+ decision_type=2 2 2 2 2 2
1729
+ left_child=2 -2 5 -3 -5 -1
1730
+ right_child=1 3 -4 4 -6 -7
1731
+ leaf_value=-0.050284394815262895 -0.051472401652421375 0.050592692579550236 -0.051200835479461297 0.050280424122202294 0.050465127310612361 -0.050459127857895095
1732
+ leaf_weight=0.17945691710337996 0.027787335216999054 0.047359100077301264 0.039564179722219706 0.19635209115222096 0.03870723582804203 0.033598298672586679
1733
+ leaf_count=32 1 5 3 36 5 4
1734
+ internal_value=8.32108e-05 0.0412364 -0.0504512 0.0503581 0.0503108 -0.0503119
1735
+ internal_weight=0.562825 0.310206 0.252619 0.282418 0.235059 0.213055
1736
+ internal_count=86 47 39 46 41 36
1737
+ is_linear=0
1738
+ shrinkage=0.05
1739
+
1740
+
1741
+ Tree=91
1742
+ num_leaves=7
1743
+ num_cat=0
1744
+ split_feature=7 1 1 7 8 9
1745
+ split_gain=0.445159 0.0997649 9.07088e-06 1.07731e-06 3.79648e-07 2.97265e-07
1746
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1747
+ decision_type=2 2 2 2 2 2
1748
+ left_child=2 -2 5 -3 -5 -1
1749
+ right_child=1 3 -4 4 -6 -7
1750
+ leaf_value=-0.050270448158576099 -0.051398526992987692 0.050563543463076947 -0.051141481764662267 0.050266675951028406 0.050442307897245721 -0.050436566562057319
1751
+ leaf_weight=0.17075086617842317 0.026469161733984947 0.045074684545397758 0.037677189335227013 0.18682568054646254 0.036835697945207357 0.031973626464605331
1752
+ leaf_count=32 1 5 3 36 5 4
1753
+ internal_value=8.32954e-05 0.0412183 -0.0504291 0.0503405 0.0502956 -0.0502966
1754
+ internal_weight=0.535607 0.295205 0.240402 0.268736 0.223661 0.202724
1755
+ internal_count=86 47 39 46 41 36
1756
+ is_linear=0
1757
+ shrinkage=0.05
1758
+
1759
+
1760
+ Tree=92
1761
+ num_leaves=7
1762
+ num_cat=0
1763
+ split_feature=7 1 1 7 8 9
1764
+ split_gain=0.423268 0.094855 7.80249e-06 9.26631e-07 3.26674e-07 2.55686e-07
1765
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1766
+ decision_type=2 2 2 2 2 2
1767
+ left_child=2 -2 5 -3 -5 -1
1768
+ right_child=1 3 -4 4 -6 -7
1769
+ leaf_value=-0.05025718836517773 -0.051328463012764294 0.05053584135422981 -0.05108509747141346 0.050253604954431264 0.050420614788711397 -0.050415120966182086
1770
+ leaf_weight=0.16246502147987485 0.025211747735738754 0.042899276595562696 0.035878210794180632 0.17775914305821061 0.035053895320743322 0.030426864977926016
1771
+ leaf_count=32 1 5 3 36 5 4
1772
+ internal_value=8.33761e-05 0.041201 -0.050408 0.0503238 0.0502811 -0.0502821
1773
+ internal_weight=0.509694 0.280924 0.22877 0.255712 0.212813 0.192892
1774
+ internal_count=86 47 39 46 41 36
1775
+ is_linear=0
1776
+ shrinkage=0.05
1777
+
1778
+
1779
+ Tree=93
1780
+ num_leaves=7
1781
+ num_cat=0
1782
+ split_feature=7 1 1 7 8 9
1783
+ split_gain=0.402461 0.0901889 6.71156e-06 7.9706e-07 2.81088e-07 2.19923e-07
1784
+ threshold=3112.1875000000005 1.5000000000000002 1.5000000000000002 3478.8119047619052 0.40833333333333338 0.071839745922519613
1785
+ decision_type=2 2 2 2 2 2
1786
+ left_child=2 -2 5 -3 -5 -1
1787
+ right_child=1 3 -4 4 -6 -7
1788
+ leaf_value=-0.050244584593455377 -0.051261996673023981 0.050509510970679308 -0.051031527960906944 0.050241177033193599 0.050399988714995715 -0.050394736323651074
1789
+ leaf_weight=0.15457929950207472 0.02401246689260006 0.04082779074087739 0.034163331147283316 0.16913049947470427 0.033357597887516022 0.028954341541975737
1790
+ leaf_count=32 1 5 3 36 5 4
1791
+ internal_value=8.34525e-05 0.0411846 -0.0503881 0.050308 0.0502673 -0.0502683
1792
+ internal_weight=0.485025 0.267328 0.217697 0.243316 0.202488 0.183534
1793
+ internal_count=86 47 39 46 41 36
1794
+ is_linear=0
1795
+ shrinkage=0.05
1796
+
1797
+
1798
+ Tree=94
1799
+ num_leaves=7
1800
+ num_cat=0
1801
+ split_feature=7 1 1 7 8 9
1802
+ split_gain=0.382686 0.0857542 8.50195e-06 6.85608e-07 2.41875e-07 1.9306e-07
1803
+ threshold=3112.1875000000005 1.5000000000000002 2.5000000000000004 3478.8119047619052 0.40833333333333338 0.071839745922519613
1804
+ decision_type=2 2 2 2 2 2
1805
+ left_child=2 -2 5 -3 -5 -1
1806
+ right_child=1 3 -4 4 -6 -7
1807
+ leaf_value=-0.050231772045157587 -0.05119893255906393 0.050484481834977696 -0.051255954612935167 0.050229362181290621 0.050380383068234058 -0.050375359261206912
1808
+ leaf_weight=0.15570021374151111 0.022868789732456207 0.038855364779010415 0.023903153836727142 0.16091879643499851 0.031742766499519348 0.027552548795938492
1809
+ leaf_count=34 1 5 1 36 5 4
1810
+ internal_value=8.35253e-05 0.041169 -0.050369 0.0502929 0.0502542 -0.0502534
1811
+ internal_weight=0.461542 0.254386 0.207156 0.231517 0.192662 0.183253
1812
+ internal_count=86 47 39 46 41 38
1813
+ is_linear=0
1814
+ shrinkage=0.05
1815
+
1816
+
1817
+ Tree=95
1818
+ num_leaves=7
1819
+ num_cat=0
1820
+ split_feature=7 1 1 7 8 9
1821
+ split_gain=0.363888 0.0815393 7.30348e-06 5.89764e-07 2.08124e-07 1.66078e-07
1822
+ threshold=3112.1875000000005 1.5000000000000002 2.5000000000000004 3478.8119047619052 0.40833333333333338 0.071839745922519613
1823
+ decision_type=2 2 2 2 2 2
1824
+ left_child=2 -2 5 -3 -5 -1
1825
+ right_child=1 3 -4 4 -6 -7
1826
+ leaf_value=-0.050220417250276289 -0.051139095953743607 0.050460690435998379 -0.051193200479213857 0.050218127763647515 0.050361738473988754 -0.050356941738795569
1827
+ leaf_weight=0.14813926233910024 0.021778276190161705 0.036977351643145084 0.022764552384614944 0.15310407569631934 0.030205549206584692 0.026218139566481113
1828
+ leaf_count=34 1 5 1 36 5 4
1829
+ internal_value=8.35957e-05 0.0411541 -0.0503509 0.0502785 0.0502418 -0.0502409
1830
+ internal_weight=0.439187 0.242065 0.197122 0.220287 0.18331 0.174357
1831
+ internal_count=86 47 39 46 41 38
1832
+ is_linear=0
1833
+ shrinkage=0.05
1834
+
1835
+
1836
+ Tree=96
1837
+ num_leaves=7
1838
+ num_cat=0
1839
+ split_feature=7 1 1 7 8 9
1840
+ split_gain=0.34602 0.0775331 6.27459e-06 5.07338e-07 1.791e-07 1.4286e-07
1841
+ threshold=3112.1875000000005 1.5000000000000002 2.5000000000000004 3478.8119047619052 0.40833333333333338 0.071839745922519613
1842
+ decision_type=2 2 2 2 2 2
1843
+ left_child=2 -2 5 -3 -5 -1
1844
+ right_child=1 3 -4 4 -6 -7
1845
+ leaf_value=-0.050209622222626926 -0.051082307401579589 0.05043807614005931 -0.051133655309017922 0.050207445200207285 0.05034401700303897 -0.050339429089042924
1846
+ leaf_weight=0.14094396075233817 0.020738586783409119 0.035189317772164941 0.021678892895579338 0.1456673052161932 0.028742266818881035 0.024947922211140394
1847
+ leaf_count=34 1 5 1 36 5 4
1848
+ internal_value=8.36615e-05 0.04114 -0.0503337 0.0502649 0.05023 -0.0502291
1849
+ internal_weight=0.417908 0.230337 0.187571 0.209599 0.17441 0.165892
1850
+ internal_count=86 47 39 46 41 38
1851
+ is_linear=0
1852
+ shrinkage=0.05
1853
+
1854
+
1855
+ Tree=97
1856
+ num_leaves=7
1857
+ num_cat=0
1858
+ split_feature=7 1 1 7 8 9
1859
+ split_gain=0.329035 0.0737251 5.39117e-06 4.36445e-07 1.54115e-07 1.229e-07
1860
+ threshold=3112.1875000000005 1.5000000000000002 2.5000000000000004 3478.8119047619052 0.40833333333333338 0.071839745922519613
1861
+ decision_type=2 2 2 2 2 2
1862
+ left_child=2 -2 5 -3 -5 -1
1863
+ right_child=1 3 -4 4 -6 -7
1864
+ leaf_value=-0.050199356445814317 -0.051028411202997205 0.050416579266307451 -0.051077146716563694 0.050197288813652312 0.050327164284452677 -0.050322783240472013
1865
+ leaf_weight=0.13409677124582231 0.01974746398627758 0.033487027743831277 0.020643839612603188 0.13859035610221326 0.027349414071068168 0.023738850373774767
1866
+ leaf_count=34 1 5 1 36 5 4
1867
+ internal_value=8.37241e-05 0.0411266 -0.0503173 0.0502519 0.0502187 -0.0502179
1868
+ internal_weight=0.397654 0.219174 0.178479 0.199427 0.16594 0.157836
1869
+ internal_count=86 47 39 46 41 38
1870
+ is_linear=0
1871
+ shrinkage=0.05
1872
+
1873
+
1874
+ Tree=98
1875
+ num_leaves=7
1876
+ num_cat=0
1877
+ split_feature=7 1 1 7 8 9
1878
+ split_gain=0.312889 0.0701055 4.63247e-06 3.75461e-07 1.3262e-07 1.05726e-07
1879
+ threshold=3112.1875000000005 1.5000000000000002 2.5000000000000004 3478.8119047619052 0.40833333333333338 0.071839745922519613
1880
+ decision_type=2 2 2 2 2 2
1881
+ left_child=2 -2 5 -3 -5 -1
1882
+ right_child=1 3 -4 4 -6 -7
1883
+ leaf_value=-0.050189596756458045 -0.050977250616091152 0.050396141654857063 -0.051023508928724509 0.050187630953487827 0.050311141473668199 -0.050306956270874226
1884
+ leaf_weight=0.12758097238838673 0.018802745267748833 0.031866437988355756 0.019657149910926819 0.13185595325194299 0.026023643789812922 0.022588017396628857
1885
+ leaf_count=34 1 5 1 36 5 4
1886
+ internal_value=8.37838e-05 0.0411139 -0.0503017 0.0502396 0.050208 -0.0502072
1887
+ internal_weight=0.378375 0.208549 0.169826 0.189746 0.15788 0.150169
1888
+ internal_count=86 47 39 46 41 38
1889
+ is_linear=0
1890
+ shrinkage=0.05
1891
+
1892
+
1893
+ Tree=99
1894
+ num_leaves=7
1895
+ num_cat=0
1896
+ split_feature=7 1 1 7 8 9
1897
+ split_gain=0.29754 0.0666646 3.98088e-06 3.23019e-07 1.14123e-07 9.09602e-08
1898
+ threshold=3112.1875000000005 1.5000000000000002 2.5000000000000004 3478.8119047619052 0.40833333333333338 0.071839745922519613
1899
+ decision_type=2 2 2 2 2 2
1900
+ left_child=2 -2 5 -3 -5 -1
1901
+ right_child=1 3 -4 4 -6 -7
1902
+ leaf_value=-0.050180315600638993 -0.050928677138001657 0.05037671632404709 -0.050972594433378086 0.050178448515061494 0.050295906560174089 -0.050291912359883201
1903
+ leaf_weight=0.12138065416365862 0.017902351915836334 0.030323687475174665 0.018716664984822273 0.12544763740152121 0.024761765031144023 0.021492652595043182
1904
+ leaf_count=34 1 5 1 36 5 4
1905
+ internal_value=8.38408e-05 0.0411018 -0.0502869 0.0502279 0.0501978 -0.0501971
1906
+ internal_weight=0.360025 0.198435 0.16159 0.180533 0.150209 0.142873
1907
+ internal_count=86 47 39 46 41 38
1908
+ is_linear=0
1909
+ shrinkage=0.05
1910
+
1911
+
1912
+ end of trees
1913
+
1914
+ feature_importances:
1915
+ Column_7=145
1916
+ Column_9=120
1917
+ Column_1=90
1918
+ Column_8=73
1919
+ Column_12=37
1920
+ Column_11=28
1921
+ Column_6=25
1922
+ Column_2=22
1923
+ Column_3=19
1924
+ Column_0=15
1925
+ Column_4=10
1926
+ Column_5=9
1927
+ Column_10=3
1928
+
1929
+ parameters:
1930
+ [boosting: gbdt]
1931
+ [objective: binary]
1932
+ [metric: binary_logloss]
1933
+ [tree_learner: serial]
1934
+ [device_type: cpu]
1935
+ [data_sample_strategy: bagging]
1936
+ [data: ]
1937
+ [valid: ]
1938
+ [num_iterations: 100]
1939
+ [learning_rate: 0.05]
1940
+ [num_leaves: 7]
1941
+ [num_threads: 8]
1942
+ [seed: 42]
1943
+ [deterministic: 0]
1944
+ [force_col_wise: 0]
1945
+ [force_row_wise: 0]
1946
+ [histogram_pool_size: -1]
1947
+ [max_depth: 4]
1948
+ [min_data_in_leaf: 5]
1949
+ [min_sum_hessian_in_leaf: 0.001]
1950
+ [bagging_fraction: 1]
1951
+ [pos_bagging_fraction: 1]
1952
+ [neg_bagging_fraction: 1]
1953
+ [bagging_freq: 0]
1954
+ [bagging_seed: 400]
1955
+ [bagging_by_query: 0]
1956
+ [feature_fraction: 1]
1957
+ [feature_fraction_bynode: 1]
1958
+ [feature_fraction_seed: 30056]
1959
+ [extra_trees: 0]
1960
+ [extra_seed: 12879]
1961
+ [early_stopping_round: 0]
1962
+ [early_stopping_min_delta: 0]
1963
+ [first_metric_only: 0]
1964
+ [max_delta_step: 0]
1965
+ [lambda_l1: 0]
1966
+ [lambda_l2: 0]
1967
+ [linear_lambda: 0]
1968
+ [min_gain_to_split: 0]
1969
+ [drop_rate: 0.1]
1970
+ [max_drop: 50]
1971
+ [skip_drop: 0.5]
1972
+ [xgboost_dart_mode: 0]
1973
+ [uniform_drop: 0]
1974
+ [drop_seed: 17869]
1975
+ [top_rate: 0.2]
1976
+ [other_rate: 0.1]
1977
+ [min_data_per_group: 100]
1978
+ [max_cat_threshold: 32]
1979
+ [cat_l2: 10]
1980
+ [cat_smooth: 10]
1981
+ [max_cat_to_onehot: 4]
1982
+ [top_k: 20]
1983
+ [monotone_constraints: ]
1984
+ [monotone_constraints_method: basic]
1985
+ [monotone_penalty: 0]
1986
+ [feature_contri: ]
1987
+ [forcedsplits_filename: ]
1988
+ [refit_decay_rate: 0.9]
1989
+ [cegb_tradeoff: 1]
1990
+ [cegb_penalty_split: 0]
1991
+ [cegb_penalty_feature_lazy: ]
1992
+ [cegb_penalty_feature_coupled: ]
1993
+ [path_smooth: 0]
1994
+ [interaction_constraints: ]
1995
+ [verbosity: -1]
1996
+ [saved_feature_importance_type: 0]
1997
+ [use_quantized_grad: 0]
1998
+ [num_grad_quant_bins: 4]
1999
+ [quant_train_renew_leaf: 0]
2000
+ [stochastic_rounding: 1]
2001
+ [linear_tree: 0]
2002
+ [max_bin: 255]
2003
+ [max_bin_by_feature: ]
2004
+ [min_data_in_bin: 3]
2005
+ [bin_construct_sample_cnt: 200000]
2006
+ [data_random_seed: 175]
2007
+ [is_enable_sparse: 1]
2008
+ [enable_bundle: 1]
2009
+ [use_missing: 1]
2010
+ [zero_as_missing: 0]
2011
+ [feature_pre_filter: 1]
2012
+ [pre_partition: 0]
2013
+ [two_round: 0]
2014
+ [header: 0]
2015
+ [label_column: ]
2016
+ [weight_column: ]
2017
+ [group_column: ]
2018
+ [ignore_column: ]
2019
+ [categorical_feature: ]
2020
+ [forcedbins_filename: ]
2021
+ [precise_float_parser: 0]
2022
+ [parser_config_file: ]
2023
+ [objective_seed: 16083]
2024
+ [num_class: 1]
2025
+ [is_unbalance: 0]
2026
+ [scale_pos_weight: 1]
2027
+ [sigmoid: 1]
2028
+ [boost_from_average: 1]
2029
+ [reg_sqrt: 0]
2030
+ [alpha: 0.9]
2031
+ [fair_c: 1]
2032
+ [poisson_max_delta_step: 0.7]
2033
+ [tweedie_variance_power: 1.5]
2034
+ [lambdarank_truncation_level: 30]
2035
+ [lambdarank_norm: 1]
2036
+ [label_gain: ]
2037
+ [lambdarank_position_bias_regularization: 0]
2038
+ [eval_at: ]
2039
+ [multi_error_top_k: 1]
2040
+ [auc_mu_weights: ]
2041
+ [num_machines: 1]
2042
+ [local_listen_port: 12400]
2043
+ [time_out: 120]
2044
+ [machine_list_filename: ]
2045
+ [machines: ]
2046
+ [gpu_platform_id: -1]
2047
+ [gpu_device_id: -1]
2048
+ [gpu_device_id_list: ]
2049
+ [gpu_use_dp: 0]
2050
+ [num_gpu: 1]
2051
+
2052
+ end of parameters
2053
+
2054
+ pandas_categorical:null
scoring/score_demo.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ score_demo.py
3
+ -------------
4
+ Score the Phase 1 demo dataset (data/synthetic/) using the trained model.
5
+
6
+ Inputs:
7
+ data/synthetic/candidate_clusters.json (fixed by Section 1)
8
+ data/synthetic/orders.json
9
+ data/synthetic/graph.gpickle
10
+ scoring/model.lgb
11
+
12
+ Output:
13
+ data/synthetic/scored_clusters.json β€” candidate_clusters.json + risk_probability
14
+
15
+ Usage:
16
+ python scoring/score_demo.py
17
+ """
18
+
19
+ import json
20
+ import os
21
+ import sys
22
+
23
+ import lightgbm as lgb
24
+ import numpy as np
25
+
26
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27
+ sys.path.insert(0, BASE_DIR)
28
+
29
+ DEMO_DIR = os.path.join(BASE_DIR, "data", "synthetic")
30
+ MODEL_PATH = os.path.join(BASE_DIR, "scoring", "model.lgb")
31
+ OUT_PATH = os.path.join(DEMO_DIR, "scored_clusters.json")
32
+
33
+
34
+ def score_demo():
35
+ from scoring.features import extract_all, _load_json, _load_graph, FEATURE_COLUMNS
36
+
37
+ clusters_path = os.path.join(DEMO_DIR, "candidate_clusters.json")
38
+ orders_path = os.path.join(DEMO_DIR, "orders.json")
39
+ graph_path = os.path.join(DEMO_DIR, "graph.gpickle")
40
+
41
+ print(f"Loading demo data from {DEMO_DIR}...")
42
+ clusters = _load_json(clusters_path)
43
+ orders = _load_json(orders_path)
44
+ graph = _load_graph(graph_path) if os.path.exists(graph_path) else None
45
+
46
+ print(f" {len(clusters)} candidate clusters")
47
+
48
+ # Extract features (no labels needed for scoring)
49
+ rows = extract_all(clusters, orders, ground_truth=None, graph=graph)
50
+ X = np.array([[r[f] for f in FEATURE_COLUMNS] for r in rows])
51
+
52
+ print(f"Loading model from {MODEL_PATH}...")
53
+ model = lgb.Booster(model_file=MODEL_PATH)
54
+
55
+ probs = model.predict(X)
56
+ print(f" Scored {len(probs)} clusters")
57
+
58
+ # Build scored_clusters.json: candidate_clusters schema + risk_probability
59
+ cluster_by_id = {c["cluster_id"]: c for c in clusters}
60
+ scored = []
61
+ for i, row in enumerate(rows):
62
+ cid = row["cluster_id"]
63
+ cluster = cluster_by_id[cid].copy()
64
+ cluster["risk_probability"] = float(probs[i])
65
+ scored.append(cluster)
66
+
67
+ with open(OUT_PATH, "w", encoding="utf-8") as f:
68
+ json.dump(scored, f, indent=2, ensure_ascii=False)
69
+
70
+ print(f"\nScored clusters saved to {OUT_PATH}")
71
+ print("\nRisk probability summary:")
72
+ for c in scored:
73
+ print(f" {c['cluster_id']:8s} size={c['size']:3d} "
74
+ f"formation={c['formation_type']:15s} "
75
+ f"risk={c['risk_probability']:.4f}")
76
+
77
+ return scored
78
+
79
+
80
+ if __name__ == "__main__":
81
+ score_demo()
scoring/train_model.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ train_model.py
3
+ --------------
4
+ Train a LightGBM binary risk classifier on the training dataset.
5
+
6
+ Outputs:
7
+ scoring/model.lgb β€” trained LightGBM model
8
+ scoring/holdout_split.json β€” held-out test rows for evaluation (Section 6)
9
+
10
+ Usage:
11
+ python scoring/train_model.py
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import pickle
17
+ import sys
18
+
19
+ import lightgbm as lgb
20
+ import numpy as np
21
+ from sklearn.model_selection import train_test_split
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Paths
25
+ # ---------------------------------------------------------------------------
26
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27
+ TRAIN_DIR = os.path.join(BASE_DIR, "data", "synthetic_train")
28
+ SCORING_DIR = os.path.join(BASE_DIR, "scoring")
29
+
30
+ MODEL_PATH = os.path.join(SCORING_DIR, "model.lgb")
31
+ HOLDOUT_PATH = os.path.join(SCORING_DIR, "holdout_split.json")
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Feature columns (must match scoring/features.py)
35
+ # ---------------------------------------------------------------------------
36
+ FEATURE_COLUMNS = [
37
+ "size",
38
+ "strong_link_count",
39
+ "weak_link_count",
40
+ "strong_link_density",
41
+ "has_same_contact_edge",
42
+ "order_count",
43
+ "total_amount",
44
+ "avg_order_amount",
45
+ "return_rate",
46
+ "chargeback_rate",
47
+ "shared_coupon_count",
48
+ "time_span_days",
49
+ "order_velocity",
50
+ "formation_type_strong",
51
+ ]
52
+
53
+ RANDOM_STATE = 42
54
+
55
+
56
+ def load_data():
57
+ """Load clusters, orders, ground truth, and graph from the training directory."""
58
+ sys.path.insert(0, BASE_DIR)
59
+ from scoring.features import extract_all, _load_json, _load_graph
60
+
61
+ clusters_path = os.path.join(TRAIN_DIR, "candidate_clusters.json")
62
+ orders_path = os.path.join(TRAIN_DIR, "orders.json")
63
+ gt_path = os.path.join(TRAIN_DIR, "ground_truth.json")
64
+ graph_path = os.path.join(TRAIN_DIR, "graph.gpickle")
65
+
66
+ clusters = _load_json(clusters_path)
67
+ orders = _load_json(orders_path)
68
+ ground_truth = _load_json(gt_path)
69
+ graph = _load_graph(graph_path) if os.path.exists(graph_path) else None
70
+
71
+ rows = extract_all(clusters, orders, ground_truth=ground_truth, graph=graph)
72
+ return rows
73
+
74
+
75
+ def train():
76
+ print("Loading training data...")
77
+ rows = load_data()
78
+ print(f" {len(rows)} candidate clusters loaded")
79
+
80
+ X = np.array([[r[f] for f in FEATURE_COLUMNS] for r in rows])
81
+ y = np.array([r["label"] for r in rows])
82
+ cluster_ids = [r["cluster_id"] for r in rows]
83
+ feature_rows = [{f: r[f] for f in FEATURE_COLUMNS} for r in rows]
84
+
85
+ pos = y.sum()
86
+ neg = len(y) - pos
87
+ print(f" Labels β€” positive: {int(pos)}, negative: {int(neg)}")
88
+
89
+ # 5.2 β€” stratified 80/20 split
90
+ (X_train, X_test, y_train, y_test,
91
+ ids_train, ids_test,
92
+ feat_train, feat_test) = train_test_split(
93
+ X, y, cluster_ids, feature_rows,
94
+ test_size=0.20,
95
+ stratify=y,
96
+ random_state=RANDOM_STATE,
97
+ )
98
+ print(f" Train: {len(X_train)} Test (holdout): {len(X_test)}")
99
+ print(f" Holdout positives: {y_test.sum():.0f} negatives: {(len(y_test)-y_test.sum()):.0f}")
100
+
101
+ # 5.3 β€” simple LightGBM model (small dataset, keep it simple)
102
+ model = lgb.LGBMClassifier(
103
+ num_leaves=7,
104
+ max_depth=4,
105
+ min_child_samples=5,
106
+ n_estimators=100,
107
+ learning_rate=0.05,
108
+ objective="binary",
109
+ random_state=RANDOM_STATE,
110
+ verbose=-1,
111
+ )
112
+ print("Training LightGBM classifier...")
113
+ model.fit(X_train, y_train)
114
+ print(" Training complete.")
115
+
116
+ # 5.4 β€” save model
117
+ model.booster_.save_model(MODEL_PATH)
118
+ print(f" Model saved to {MODEL_PATH}")
119
+
120
+ # Verify reload
121
+ loaded = lgb.Booster(model_file=MODEL_PATH)
122
+ test_pred = loaded.predict(X_test)
123
+ print(f" Model reload verified (test pred shape: {test_pred.shape})")
124
+
125
+ # 5.5 β€” feature importances
126
+ importances = model.feature_importances_
127
+ importance_pairs = sorted(
128
+ zip(FEATURE_COLUMNS, importances),
129
+ key=lambda x: x[1], reverse=True
130
+ )
131
+ print("\nFeature importances (gain):")
132
+ for feat, imp in importance_pairs:
133
+ print(f" {feat:30s}: {imp}")
134
+ if importance_pairs[0][0] == "size" and importance_pairs[0][1] > sum(importances) * 0.5:
135
+ print(" [NOTE] 'size' dominates feature importance β€” worth flagging in summary")
136
+
137
+ # Save holdout split (cluster IDs + features + labels + probabilities)
138
+ holdout = []
139
+ probs = loaded.predict(X_test)
140
+ for i in range(len(ids_test)):
141
+ holdout.append({
142
+ "cluster_id": ids_test[i],
143
+ "features": feat_test[i],
144
+ "label": int(y_test[i]),
145
+ "risk_probability": float(probs[i]),
146
+ })
147
+
148
+ with open(HOLDOUT_PATH, "w", encoding="utf-8") as f:
149
+ json.dump(holdout, f, indent=2, ensure_ascii=False)
150
+ print(f" Holdout split saved to {HOLDOUT_PATH} ({len(holdout)} rows)")
151
+
152
+ return model, holdout, importance_pairs
153
+
154
+
155
+ if __name__ == "__main__":
156
+ train()