@1aboveio/skills 0.20.2 → 0.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +14 -6
  2. package/package.json +1 -1
  3. package/runtime/skills/distribution/generated/recipes.json +220 -25
  4. package/runtime/skills/distribution/scripts/bundles.mjs +167 -48
  5. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -0
  6. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
  7. package/skills/compliance/compliance-audit/LICENSE +3 -0
  8. package/skills/compliance/compliance-audit/SKILL.md +466 -0
  9. package/skills/compliance/compliance-audit/references/findings-schema.md +290 -0
  10. package/skills/compliance/compliance-audit/references/vars-checklist.md +1068 -0
  11. package/skills/compliance/compliance-audit/references/vars-findings-schema.md +395 -0
  12. package/skills/compliance/compliance-audit/scripts/reconcile_tracker.py +454 -0
  13. package/skills/compliance/compliance-audit/scripts/test_validate_tracker.py +90 -0
  14. package/skills/compliance/compliance-audit/scripts/validate_tracker.py +752 -0
  15. package/skills/compliance/doc-authenticity/LICENSE +3 -0
  16. package/skills/compliance/doc-authenticity/SKILL.md +158 -0
  17. package/skills/compliance/doc-authenticity/references/agreement.md +66 -0
  18. package/skills/compliance/doc-authenticity/references/bank-statement.md +150 -0
  19. package/skills/compliance/doc-authenticity/references/forensic-signals.md +157 -0
  20. package/skills/compliance/doc-authenticity/references/image-signals.md +114 -0
  21. package/skills/compliance/doc-authenticity/references/invoice.md +66 -0
  22. package/skills/compliance/doc-authenticity/references/issuer-profiles.md +86 -0
  23. package/skills/compliance/doc-authenticity/references/verdict-and-report.md +95 -0
  24. package/skills/compliance/doc-authenticity/references/verdict.schema.json +81 -0
  25. package/skills/compliance/doc-authenticity/scripts/image_forensics.py +294 -0
  26. package/skills/compliance/doc-authenticity/scripts/pdf_forensics.py +401 -0
  27. package/skills/compliance/doc-authenticity/scripts/validate_codes.py +224 -0
  28. package/skills/compliance/doc-authenticity/scripts/validate_verdict.py +308 -0
  29. package/skills/compliance/report-writing/LICENSE +3 -0
  30. package/skills/compliance/report-writing/SKILL.md +206 -0
  31. package/skills/compliance/report-writing/references/audit-report-template.md +121 -0
  32. package/skills/compliance/report-writing/references/external-assessment-template.md +167 -0
  33. package/skills/compliance/report-writing/references/management-report-template.md +145 -0
  34. package/skills/compliance/report-writing/references/vars-report-template.md +224 -0
  35. package/skills/document-utils/doc-to-md/LICENSE +3 -0
  36. package/skills/document-utils/doc-to-md/SKILL.md +77 -0
  37. package/skills/document-utils/doc-to-md/scripts/convert.py +157 -0
  38. package/skills/document-utils/reg-parser/LICENSE +3 -0
  39. package/skills/document-utils/reg-parser/SKILL.md +201 -0
  40. package/skills/document-utils/reg-parser/references/checklist-schema.md +223 -0
  41. package/skills/document-utils/reg-parser/references/checklists/hk-amlo-checklist.json +1306 -0
  42. package/skills/document-utils/reg-parser/references/checklists/hk-amlo-checklist.md +469 -0
  43. package/skills/document-utils/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.json +634 -0
  44. package/skills/document-utils/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.md +233 -0
  45. package/skills/document-utils/reg-parser/references/checklists/hk-mso-guideline-checklist.json +2194 -0
  46. package/skills/document-utils/reg-parser/references/checklists/hk-mso-guideline-checklist.md +771 -0
  47. package/skills/document-utils/reg-parser/references/checklists/kyc-aml-checklist.md +258 -0
  48. package/skills/document-utils/reg-parser/references/checklists/visa-vars-checklist.md +1058 -0
  49. package/skills/document-utils/reg-parser/references/mandates/hk-amlo-cap615.md +389 -0
  50. package/skills/document-utils/reg-parser/references/mandates/visa-vars.md +205 -0
  51. package/skills/document-utils/reg-parser/scripts/compare_checklist.py +228 -0
  52. package/skills/document-utils/reg-parser/scripts/dump_checklist.py +348 -0
  53. package/skills/document-utils/reg-parser/scripts/preprocess-mandate.sh +171 -0
  54. package/skills/document-utils/reg-parser/scripts/test_quarantine.sh +102 -0
  55. package/skills/document-utils/reg-parser/scripts/validate_checklist.py +532 -0
  56. package/skills/document-utils/reg-parser/scripts/validate_mandate.py +242 -0
  57. package/skills/engineering/engineering-runtime/coherence/workflow.json +16 -16
  58. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -0
  59. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
  60. package/skills/payment/similar-domain/LICENSE +3 -0
  61. package/skills/payment/similar-domain/SKILL.md +178 -0
  62. package/skills/payment/similar-domain/references/entities.md +58 -0
  63. package/skills/payment/similar-domain/references/fingerprints.md +82 -0
  64. package/skills/payment/similar-domain/references/pivots.md +66 -0
  65. package/skills/payment/similar-domain/references/report-template.md +76 -0
  66. package/skills/payment/similar-domain/references/scoring.md +61 -0
  67. package/skills/payment/similar-domain/references/terminology.md +37 -0
  68. package/skills/payment/similar-domain/references/workflow.md +79 -0
  69. package/skills/payment/tld-detection/SKILL.md +76 -21
  70. package/skills/payment/tld-detection/references/clean.md +1 -1
  71. package/skills/payment/tld-detection/references/clustering.md +21 -6
  72. package/skills/payment/tld-detection/references/decision.md +7 -2
  73. package/skills/payment/tld-detection/references/fx-conversion.md +229 -0
  74. package/skills/payment/tld-detection/references/report-template.md +63 -27
  75. package/skills/payment/tld-detection/references/streams.md +90 -0
  76. package/skills/payment/tld-detection/references/terminology.md +15 -7
  77. package/skills/payment/tld-detection/references/workflow.md +1 -1
  78. package/skills/payment/tld-detection/scripts/detect_streams.py +542 -0
  79. package/skills/engineering/ensure-coverage/evals/evals.json +0 -125
  80. package/skills/engineering/rush-issues/evals/evals.json +0 -65
  81. package/skills/engineering/rush-release/evals/evals.json +0 -58
  82. package/skills/fullstack/shadcn/evals/evals.json +0 -90
  83. package/skills/payment/fraud-analysis/evals/evals.json +0 -40
  84. package/skills/payment/tld-detection/evals/evals.json +0 -107
  85. package/skills/payment/tld-detection/tests/test_detect_ladders.py +0 -132
@@ -21,6 +21,11 @@ Risk uses independent post-membership evidence:
21
21
  - transaction-date and BIN-country-local-hour differences versus that baseline
22
22
  - externally evidenced MCC or merchant-business mismatch; never infer this
23
23
  from a suggestive descriptor name alone
24
+ - stream evidence from [streams.md](streams.md): a wallet currency that is not
25
+ the card country's legal tender, a frozen-rate stream beside live-rate
26
+ groups on the same rungs and days, one attempt per card with no retry after
27
+ decline, burst concentration, a whole country off-menu, a retail constant
28
+ with no state variation
24
29
 
25
30
  Declare portfolio-specific thresholds for a materially different rate,
26
31
  distribution, or descriptor share in the run methodology before assigning
@@ -39,7 +44,7 @@ country-slice coverage = cluster attempts in country /
39
44
  all attempts in the same descriptor and country
40
45
  ```
41
46
 
42
- An exact **100% single-BIN-country cluster** is a standout characteristic.
47
+ An exact **100% single-BIN-country cluster** is a standout characteristic in the canonical original-currency run. In the BIN-country base-currency sensitivity, BIN country constructs the target currency and is therefore not independent evidence; report 100% concentration descriptively but do not use it to raise the FX risk tier.
43
48
  Country-slice coverage explains how large that concentration is inside the
44
49
  descriptor-country population, but it has no fixed threshold and contributes
45
50
  no independent risk points. Values such as 20% or 33.8% are descriptive, not
@@ -52,7 +57,7 @@ Assign exactly one tier to every identified currency-amount pair:
52
57
 
53
58
  | Tier | Use when |
54
59
  |---|---|
55
- | High | An exact 100% single-country standout has at least one independent corroborating risk characteristic, or several independently strong non-country characteristics converge |
60
+ | High | In the canonical run, an exact 100% single-country standout has at least one independent corroborating risk characteristic, or several independently strong non-country characteristics converge; FX concentration cannot raise tier |
56
61
  | Medium | At least two independent characteristics materially differ from the same-descriptor baseline, but High is not justified |
57
62
  | Low | The structural cluster gate is met, but observed corroborating evidence is limited or not material |
58
63
 
@@ -0,0 +1,229 @@
1
+ # BIN-country base-currency FX sensitivity
2
+
3
+ Use this supplemental path when the user asks to convert transaction amounts
4
+ to the card-origin currency, or when daily FX drift may conceal fixed local-
5
+ currency anchors. Run it after canonical original-currency membership is
6
+ complete. It is sensitivity evidence, not a replacement clustering universe.
7
+
8
+ ## Separation contract
9
+
10
+ - Reuse the canonical cleaned authorization rows and descriptor normalization.
11
+ - Keep canonical `C*` results unchanged.
12
+ - Name supplemental families `FXC1`, `FXC2`, ... and publish separate files
13
+ such as `<scope>_bin_base_currency_clusters.md` and
14
+ `<scope>_bin_base_currency_methodology.md`.
15
+ - Do not combine `C*` and `FXC*` attempts into one exposure total because the
16
+ same authorization may belong to both views.
17
+
18
+ ## Target currency
19
+
20
+ Map normalized BIN country to the legal tender in effect on the transaction
21
+ date. Use the ISO 4217 Maintenance Agency's current and historic lists as the
22
+ preferred source for currency and minor unit, and record the complete run-
23
+ specific country-to-currency map and source.
24
+ Do not infer currency from language, descriptor, terminal country, shopper IP,
25
+ or transaction currency. For countries with several legal tenders or a
26
+ currency transition during the window, declare and justify the dated mapping;
27
+ exclude ambiguous rows rather than silently choosing one.
28
+
29
+ Missing BIN country remains in the canonical run but cannot be assigned a BIN-
30
+ origin currency. Count it as an FX exclusion under
31
+ `missing country-currency mapping`.
32
+
33
+ ## Frankfurter v2 rates and cache contract
34
+
35
+ Frankfurter v2 is mandatory for this sensitivity. Its public API requires no
36
+ API key or other authentication. Before retrieval, choose and disclose either
37
+ `default blended` or one or more named provider keys. The default blend is a
38
+ real provider-selection decision: omit the `providers` filter from the URL and
39
+ state that Frankfurter blended all eligible providers. For a named selection,
40
+ append `&providers=<comma-separated-provider-keys>` and record those exact
41
+ filter keys. In both modes, `expand=providers` is mandatory so the response
42
+ identifies the providers that actually contributed to each non-peg row.
43
+
44
+ Every request uses this exact endpoint and includes all five required query
45
+ parameters, even for a one-day window. A named-provider request also includes
46
+ the optional `providers` filter:
47
+
48
+ ```text
49
+ GET https://api.frankfurter.dev/v2/rates?from=<YYYY-MM-DD>&to=<YYYY-MM-DD>&base=<P>&quotes=<comma-separated-currencies>&expand=providers
50
+ GET https://api.frankfurter.dev/v2/rates?from=<YYYY-MM-DD>&to=<YYYY-MM-DD>&base=<P>&quotes=<comma-separated-currencies>&expand=providers&providers=<comma-separated-provider-keys>
51
+ ```
52
+
53
+ `from` and `to` are the inclusive UTC transaction-date bounds, `base` is the
54
+ declared pivot currency `P`, and `quotes` is the sorted, deduplicated set of
55
+ source and target currencies needed by the run. `expand=providers` requests
56
+ row-level contributing-provider keys; it does not select a provider and does
57
+ not replace the optional `providers` filter. Do not use the v1 nested response,
58
+ `/latest`, or an implicit latest date. A returned row means that its `rate` is
59
+ quote-currency units per one unit of its `base` currency: with `base=P`,
60
+ `rate(d, P→Q)` is units of `Q` per one unit of pivot `P`.
61
+
62
+ Write the response to
63
+ `reports/tld-detection/<scope>_frankfurter_v2_rates.cache.json`. Create this
64
+ run-specific cache once, do not overwrite it, and record its SHA-256 in the
65
+ methodology. The cache schema is versioned and contains at least:
66
+
67
+ ```json
68
+ {
69
+ "schema_version": 1,
70
+ "api": "Frankfurter",
71
+ "api_version": "v2",
72
+ "endpoint": "https://api.frankfurter.dev/v2/rates",
73
+ "cache_created_at_utc": "<ISO-8601 UTC timestamp ending Z>",
74
+ "requests": [
75
+ {
76
+ "request_url": "<complete run-specific request URL>",
77
+ "retrieved_at_utc": "<ISO-8601 UTC timestamp ending Z>",
78
+ "provider_selection": {"mode": "default blended", "providers": []},
79
+ "query": {"from": "<date>", "to": "<date>", "base": "<P>", "quotes": ["<Q>"], "expand": ["providers"]},
80
+ "returned_facts": [
81
+ {"date": "<date>", "base": "<P>", "quote": "<Q>", "rate": "<positive decimal>", "providers": ["<contributing-provider-key>"]}
82
+ ]
83
+ }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ Preserve every returned `date`, `base`, `quote`, and unrounded `rate` fact and,
89
+ when Frankfurter returns it, the complete `providers` key array exactly as
90
+ returned. An omitted `providers` member means Frankfurter marked that fact as
91
+ peg-derived; it is not unknown provenance. Preserve the omission rather than
92
+ writing `[]`, `null`, a guessed provider, or the request's selected providers.
93
+ For named-provider requests, `provider_selection` and the optional URL/query
94
+ filter record what was requested; add `"providers": ["<provider-key>"]` to the
95
+ cached `query`. Each fact's `providers` array records which of those providers
96
+ actually contributed; peg-derived facts still omit the member. The methodology
97
+ repeats the cache path, complete request URL or
98
+ URLs, UTC retrieval timestamp or timestamps, provider selection, query
99
+ bounds/pivot/quotes/expansion, row-level provider identities and peg-derived
100
+ omissions, schema version, and cache SHA-256. Never rely on a later live refetch
101
+ to reproduce a run.
102
+
103
+ Fail closed before conversion when the cache is missing; `schema_version` is
104
+ missing or unsupported; a legacy/v1 cache is supplied; required metadata or a
105
+ `date`/`base`/`quote`/`rate` fact is absent; the endpoint, complete URL, parsed
106
+ query (including `expand=providers` and any provider filter), provider
107
+ selection, retrieval timestamp, requested window, pivot, or quotes do not match
108
+ the run; a present fact-level `providers` member is not an array of provider
109
+ keys; a returned provider key was not preserved; a rate is non-positive; or
110
+ duplicate facts conflict. Do not repair a cache mismatch by refetching during
111
+ analysis and do not treat an invalid cache as ordinary row-level FX exclusions.
112
+
113
+ ## Conversion
114
+
115
+ For each authorization on UTC transaction date `d`, with source currency `S`,
116
+ target BIN-origin currency `T`, source amount `a`, and API quotes expressed as
117
+ currency units per pivot currency `P`:
118
+
119
+ ```text
120
+ base_amount = a / rate(d, P→S) × rate(d, P→T)
121
+ ```
122
+
123
+ Use `1` when `S = P` or `T = P`. If either required non-identity quote has no
124
+ returned fact whose `date` is exactly `d`, exclude that authorization. Never
125
+ fill weekends or missing dates with a prior-business-day, monthly average,
126
+ latest, or next-day rate.
127
+
128
+ Count exclusions by reason, date, and currency. Report:
129
+
130
+ ```text
131
+ canonical cleaned attempts → exact-date converted attempts → FX exclusions
132
+ ```
133
+
134
+ ## Converted partition universe
135
+
136
+ For each frozen canonical descriptor partition `D`, define:
137
+
138
+ ```text
139
+ P_FX(D) = authorization attempts in D successfully converted using exact-date
140
+ Frankfurter v2 facts under the validated cache contract
141
+ |P_FX| = the attempt count of P_FX(D) for the current descriptor partition
142
+ ```
143
+
144
+ Identity legs valued at `1` remain exact-date conversions; every non-identity
145
+ leg must have the matching returned date/base/quote/rate fact. Rows excluded
146
+ for mapping, quote, or conversion reasons are not in `P_FX`.
147
+
148
+ Compute FX candidate counts and both FX thresholds only from `P_FX` in the
149
+ current descriptor:
150
+
151
+ | Set | FX membership |
152
+ |---|---|
153
+ | High-count (seeds) | union of (a) FX pairs with count ≥ `max(30, 0.5% of \|P_FX\|)`, (b) the `P_FX` partition top 25 FX pairs by count, and (c) per target currency in `P_FX`, the top 10 FX pairs with count ≥30 |
154
+ | Rung pool | FX pairs with count ≥ `max(5, 0.1% of \|P_FX\|)` |
155
+
156
+ State the resulting thresholds per descriptor. FX-excluded rows never enter
157
+ these denominators, rankings, rung pools, families, baselines, or cluster
158
+ metrics. They remain only in exclusion coverage and reconciliation back to
159
+ canonical `U`:
160
+
161
+ ```text
162
+ |U_D| = |P_FX(D)| + |FX exclusions in D|
163
+ |U| = sum_D |P_FX(D)| + all FX exclusions
164
+ ```
165
+
166
+ ## Rounding and keys
167
+
168
+ Round converted values once, half-up, to the target currency's ISO 4217 minor
169
+ unit in effect on the transaction date. Record the exponent table actually
170
+ used. Do not round rates before conversion.
171
+
172
+ Inside each frozen descriptor partition, define the supplemental key as:
173
+
174
+ ```text
175
+ (target BIN-origin currency, rounded base_amount)
176
+ ```
177
+
178
+ Apply the fixed 2x, three-level, multi-anchor support, disjointness, and ≥100
179
+ exact-member gates as the canonical detector after applying the `P_FX`-only
180
+ high-count and rung-pool thresholds above. Final FX membership is exact
181
+ equality on descriptor, target currency, and rounded base amount; tolerance is
182
+ discovery-only.
183
+
184
+ Scale absolute residual caps in target-currency minor units. A defensible
185
+ starting sensitivity grid is 1, 2, and 5 minor units, retaining the smallest
186
+ cap under which material paths are stable. Report currency-specific cluster
187
+ counts and covered attempts; do not repeat the portfolio-wide total on every
188
+ currency row.
189
+
190
+ ## Baseline, risk, and reporting
191
+
192
+ For each `FXC*` family, the comparison baseline uses only `P_FX` for the same
193
+ frozen canonical descriptor and removes that family's exact FX keys. FX-
194
+ excluded rows cannot enter the same-descriptor baseline. Show exposure against
195
+ both the converted universe and all canonical cleaned authorization attempts.
196
+ Reconcile identified FX attempts + converted baseline + FX exclusions to the
197
+ canonical cleaned universe.
198
+
199
+ BIN country causally determines target currency in this view. Therefore:
200
+
201
+ - show BIN-country composition and descriptor-country slice coverage;
202
+ - caption exact 100% single-country concentration as descriptive and
203
+ constructed by the target-currency mapping;
204
+ - do not count country concentration as independent risk evidence;
205
+ - do not raise the FX tier because of country concentration;
206
+ - assign High/Medium/Low using only structural and other independent
207
+ `P_FX` same-descriptor characteristics.
208
+
209
+ Use the consolidated per-cluster table in [report-template.md](report-template.md).
210
+ Keep the anchor table and BIN-country attribution table separate because they
211
+ are one-to-many detail, not duplicate cluster facts. Include the same four
212
+ charts as the canonical report and label converted anchors with their target
213
+ base currency.
214
+
215
+ ## FX audit checklist
216
+
217
+ - [ ] Canonical cleaned universe and `C*` results were frozen first
218
+ - [ ] As-of-date country-to-legal-tender mapping and minor units are recorded
219
+ - [ ] Frankfurter `/v2/rates` uses explicit `from`, `to`, `base`, `quotes`, and `expand=providers`; authentication is not required
220
+ - [ ] Provider selection/default blend, complete request URL, UTC retrieval time, returned facts with contributing provider keys or documented peg-derived omission, cache path/SHA-256, and supported cache schema version are recorded
221
+ - [ ] Missing, mismatched, incomplete, or legacy cache facts fail closed before conversion
222
+ - [ ] Formula supports non-pivot source currencies
223
+ - [ ] Exact-date no-fill coverage and every exclusion are reconciled
224
+ - [ ] Per descriptor, `|P_FX|` is only exact-date-converted attempts; thresholds and same-descriptor baselines use only `P_FX`
225
+ - [ ] `FXC*` membership uses exact descriptor + target currency + rounded amount
226
+ - [ ] Caps and sensitivity are expressed per currency in minor units
227
+ - [ ] Country concentration is descriptive and excluded from FX risk evidence
228
+ - [ ] Converted and all-authorization exposure denominators are both shown
229
+ - [ ] `C*` and `FXC*` exposure are never added together
@@ -18,30 +18,31 @@ writing, grep banned strings in [terminology.md](terminology.md).
18
18
  English Markdown headings on the left; Chinese HTML / `.zh.md` on the right.
19
19
 
20
20
  ```markdown
21
- # Potential TLD currency-amount pairs — <window>
22
- # 潜在 TLD 币种金额对 — <window>
21
+ # Potential transaction laundering (TLD) currency-amount pairs — <window>
22
+ # 潜在交易清洗(TLD)币种金额对 — <window>
23
23
 
24
24
  ## TLD Risk Assessment # TLD 风险评估
25
25
  [Separate methodology link] # [独立方法文档链接]
26
+ ## Upstream Streams # 上游流
26
27
  ## High-Risk Clusters # 高风险 Cluster
27
28
  ## TLD Risk Exposure # TLD 风险敞口
28
29
  ### By risk tier and descriptor # 按风险等级及 Descriptor
29
30
  ### Overall exposure # 整体风险敞口
30
31
  ## All Identified Currency-Amount Pairs # 全部识别出的币种金额对
31
32
  ### Currency-Amount Pair C1 # 币种金额对 C1
32
- #### Definition # 定义
33
- #### Characteristics # 特征
33
+ #### Cluster Profile and Comparison # Cluster 概况与对比
34
34
  ## Verdict # 结论
35
35
  ```
36
36
 
37
37
  The separate methodology document uses this structure:
38
38
 
39
39
  ```markdown
40
- # TLD currency-amount-pair detection methodology — <scope/window>
41
- # TLD 币种金额对识别方法 — <范围/时段>
40
+ # Transaction laundering (TLD) currency-amount-pair detection methodology — <scope/window>
41
+ # 交易清洗(TLD)币种金额对识别方法 — <范围/时段>
42
42
 
43
43
  ## Universe and cleaning # 分析总体与清洗
44
44
  ## Descriptor partition logic # Descriptor 分区逻辑
45
+ ## Stream inference logic # 上游流推断逻辑
45
46
  ## Currency-amount-pair logic # 币种金额对逻辑
46
47
  ## Baseline and audit details # 基线与审计明细
47
48
  ```
@@ -88,6 +89,21 @@ descriptor-local high-count rule, baseline definitions, below-threshold
88
89
  families, risk-tier rules, all run-specific materiality thresholds, and files
89
90
  used. No skill name in the HTML footer; filenames only.
90
91
 
92
+ ### Upstream streams / 上游流
93
+
94
+ One table, one row per stream from `detect_streams.py`, per descriptor:
95
+ stream label (wallet currency, menu scheme, live or frozen; retail constant;
96
+ off-menu), attempts, share of descriptor, top countries, approval rate,
97
+ suspected-fraud share, repeat-card share, retry after decline, top-three-hour
98
+ share, address-verification presence. Under the table, at most three
99
+ sentences: how many upstreams the descriptor carries, which stream each
100
+ cluster below belongs to, and which streams (frozen, off-menu, retail) are
101
+ evidence in their own right. A frozen-rate stream is described as generated
102
+ traffic with the drift comparison against live groups on the same rungs and
103
+ days; it is not called laundering, fraud or a bot. Methodology holds the
104
+ parameters, rates cache, per-country wallet coverage and drift, and the
105
+ alternatives rejected.
106
+
91
107
  ### High-risk clusters / 高风险 Cluster
92
108
 
93
109
  Include High-risk clusters only. For each, show descriptor, representative
@@ -107,30 +123,42 @@ or “supported TLD” impact figure.
107
123
 
108
124
  ### Currency-amount-pair analysis / 全部识别出的币种金额对
109
125
 
110
- One subsection per identified currency-amount pair. Definition contains only
111
- Descriptor, seed, representative anchors, and cluster attempts. Express
112
- anchors as `USD 11.7 USD 23.39* USD 46.78`. Follow that with an anchor
113
- table whose columns are anchor, attempts, authorization success rate, and
114
- suspected fraud as share of attempts. Always compute both rates for every
115
- theoretical level from that level's exact member attempts, including levels
116
- with fewer than the contrast min-n (default 30). Do not write `n/a` on this
117
- table because the rung is small. For a level containing multiple nearby exact
118
- values, display its highest-count amount with `*`; the table totals attempts
119
- and rates across that level and explains `*`.
120
-
121
- Show the risk tier and concise basis immediately below the cluster heading.
122
- Characteristics contain Descriptor, descriptor total attempts, cluster
123
- attempts, cluster share of descriptor, and MCC. Then compare Cluster with the
124
- baseline formed by removing that cluster from the same descriptor. The
125
- comparison contains authorization success rate, suspected fraud as a share of
126
- attempts, Top 3 issuing banks, and Top 3 BIN countries.
126
+ One subsection per identified currency-amount pair. Show the risk tier and
127
+ concise basis immediately below the cluster heading, then use one consolidated
128
+ profile/comparison table. Do not emit separate Definition, Characteristics,
129
+ Specification, and Metrics tables because they repeat descriptor, attempts,
130
+ and cluster context.
131
+
132
+ The consolidated table has columns `Section`, `Item`, `Cluster / value`, and
133
+ `Same-descriptor baseline`. It contains each fact exactly once:
134
+
135
+ | Section | Items |
136
+ |---|---|
137
+ | Definition | Descriptor, seed, representative anchors |
138
+ | Population | Descriptor attempts, cluster attempts, cluster share of descriptor |
139
+ | Specification | MCC |
140
+ | Metrics | Authorization success rate, suspected fraud as share of attempts, Top 3 issuing banks, Top 3 BIN countries |
141
+
142
+ Use `—` in the baseline column for definition/specification values that have no
143
+ comparison. The baseline population is the same descriptor after removing the
144
+ cluster's exact keys. Express anchors as `USD 11.7 → USD 23.39* → USD 46.78`.
145
+
146
+ Follow the consolidated table with one anchor table whose columns are anchor,
147
+ attempts, authorization success rate, and suspected fraud as share of attempts.
148
+ Always compute both rates for every theoretical level from that level's exact
149
+ member attempts, including levels with fewer than the contrast min-n (default
150
+ 30). Do not write `n/a` on this table because the rung is small. For a level
151
+ containing multiple nearby exact values, display its highest-count amount with
152
+ `*`; the table totals attempts and rates across that level and explains `*`.
127
153
 
128
154
  For every Top 3 cluster BIN country, add a table with country, cluster attempts,
129
155
  share of cluster, total attempts in the same descriptor-country slice, and
130
- cluster coverage of that slice. Mark exact 100% single-country composition as a
131
- standout. Treat `Mixed` as neutral. Do not assign a threshold or risk points to
132
- country-slice coverage, and do not promote any composition below 100% merely
133
- because it is high.
156
+ cluster coverage of that slice. In canonical original-currency results only,
157
+ mark exact 100% single-country composition as a standout. Treat `Mixed` as
158
+ neutral. Do not assign a threshold or risk points to country-slice coverage,
159
+ and do not promote any composition below 100% merely because it is high. In FX
160
+ results, caption even 100% concentration as descriptive because BIN country
161
+ constructs target currency; it cannot raise the FX tier.
134
162
 
135
163
  Follow the tables with transaction-date and transaction-time charts comparing
136
164
  the cluster with the same-descriptor baseline. These are characteristics, not
@@ -140,6 +168,14 @@ When clustering finds no qualifying family, omit currency-amount-pair subsection
140
168
  publish the ranking and below-threshold candidates in the methodology
141
169
  document, show zero identified-cluster exposure, and still write Verdict.
142
170
 
171
+ For an FX sensitivity, keep the same consolidated layout, label anchors as
172
+ base-currency anchors, and link separate `<scope>_bin_base_currency_clusters*`
173
+ and methodology files. Define `|P_FX|` as the exact-date-converted attempts in
174
+ the current frozen canonical descriptor partition. Use only `P_FX` for FX
175
+ high-count/rung-pool thresholds and the same-descriptor baseline; keep excluded
176
+ rows only in exclusion coverage and reconciliation to canonical `U`. State both
177
+ converted-universe and all-authorization exposure denominators.
178
+
143
179
  ### Verdict / 结论
144
180
 
145
181
  [decision.md](decision.md). **≤3 sentences** for the whole book, then an
@@ -0,0 +1,90 @@
1
+ # Stream inference
2
+
3
+ Run after cleaning and before clustering. A descriptor is a billing name, not a
4
+ merchant: one descriptor can carry several upstreams. Exact-amount ladders miss
5
+ most of them, because a cashier that converts a wallet-currency preset to the
6
+ processing currency at a live rate turns one €10 preset into a dozen exact
7
+ amounts across days. Stream inference works in the wallet currency instead.
8
+
9
+ ## Script
10
+
11
+ ```bash
12
+ python3 {baseDir}/scripts/detect_streams.py cleaned-authorizations.csv \
13
+ --rates rates_cache.json --fetch-rates 2026-07-01 2026-08-31 \
14
+ --amount-column amount --currency-column currency --date-column timestamp \
15
+ --descriptor-column descriptor --bin-country-column bin_country \
16
+ --response-column response --approval-values A001 --suspected-fraud-values D102 \
17
+ --account-column account --card-type-column card_type --avs-column avs --auth-column auth \
18
+ --output streams.json --markdown streams.md
19
+ ```
20
+
21
+ `--rates` is a daily cache `{base, rates:{YYYY-MM-DD:{CCY:rate}}}` quoted as
22
+ units per one base unit; `--fetch-rates` fills it from Frankfurter v1 once.
23
+ Prior-day carry (default 7 days) covers weekends; carried days are counted in
24
+ the output. Pegged currencies (SAR, AED, QAR, KWD, BHD, OMR, HKD) are built in.
25
+ This cache is for discovery; the audit FX path in
26
+ [fx-conversion.md](fx-conversion.md) keeps its own contract.
27
+
28
+ ## What it does, per descriptor and BIN-country group (default ≥80 attempts)
29
+
30
+ 1. Retail list, strong test: one constant `k` in 1.00 to 1.15 under which at
31
+ least 30% of amounts are retail-shaped (`x.99`, `x.95`, `x.98`, `x.00`) with
32
+ at least 8 distinct base prices, against a control that jitters amounts by
33
+ 0.4 to 2%. Rows on the list become a `retail x<k>` stream.
34
+ 2. Wallet inference: for each candidate currency (legal tender of the card
35
+ country, EUR, USD, GBP, CHF), convert amounts at the day's rate and find
36
+ peaks within 1.2%; coverage is the share of rows on the top ten peaks, the
37
+ control re-measures jittered rows against the same centres, and day drift is
38
+ how much the daily median of each peak moves. Prefer the currency whose
39
+ peaks land on a known menu, then the lowest drift, then legal tender, EUR,
40
+ USD. A wallet is accepted at 45% coverage, or at 15% coverage when the peaks
41
+ sit on a menu (a minority stream inside a mixed group).
42
+ 3. Menu scheme: peaks are matched (1.5% tolerance) against the 10-scheme
43
+ (10, 20, 25, 30, 40, 50, 100 and multiples, with a scale of 1, 5, 10, 25,
44
+ 100, 400 or 1000 for small-unit currencies) and the 12-scheme (12, 24, 36,
45
+ 48, 72, 96, 108, 144, 192, 288, 384). When a scheme covers at least half of
46
+ the peak attempts, only the on-menu rows form the stream; off-menu peaks stay
47
+ in the residual for the next round.
48
+ 4. Rate consistency: for each rung with at least 3 rows on at least 4 days,
49
+ the stream is `frozen` when the processed amount is identical to the cent on
50
+ every day while the market moved at least 0.1%; otherwise `live`. Players on
51
+ a live cashier drift; a script that fetched one rate does not.
52
+ 5. Weak retail test on what remains (20% share, 5 distinct bases), then
53
+ `off-menu` for the residual.
54
+
55
+ Up to three rounds per group. Every threshold is a CLI option and is echoed in
56
+ the JSON under `parameters`.
57
+
58
+ ## Reading the output
59
+
60
+ | Label | Meaning | Treat as |
61
+ |---|---|---|
62
+ | `EUR menu-10 live` | wallet presets converted live near the market rate | player deposits on a euro-priced cashier |
63
+ | `AUD menu-10 live`, `CZK menu-10 live` | local-currency menus per market | the same cashier's local settings |
64
+ | `EUR menu-12 frozen` | preset menu at one rate for days | generated traffic; contrast its behaviour |
65
+ | `retail x1.06` | list prices times one constant, many distinct bases | a shop checkout; the constant is a fake tax line or a stale rate, not a fee to interpret |
66
+ | `GBP menu-none live` | peaks without a known scheme | a menu not in the built-in schemes, or a mixture; inspect peaks |
67
+ | `off-menu` | no peak structure | typed amounts or another upstream; a whole country off-menu is a finding |
68
+
69
+ Behaviour per stream: attempts, cards, repeat-card share, retry within 30
70
+ minutes after a decline, top-three-hour share (UTC), burst gap regularity,
71
+ address verification present, 3-D Secure present, prepaid share, top-five BIN
72
+ share, approval, suspected fraud. On the reference book, retry after decline
73
+ separated players (31% casino, 45% shop) from a generated batch (0.4%) more
74
+ sharply than approval rate did.
75
+
76
+ ## Rules
77
+
78
+ - Infer the wallet before interpreting any constant. Two apparent fees on the
79
+ reference book (5.75% and 12.1%) were AUD and EUR conversions.
80
+ - A shared numeric menu across several wallet currencies is a cashier
81
+ fingerprint; two menus in one descriptor are two cashiers.
82
+ - A country whose cards are charged in a currency that is not its legal
83
+ tender (Saudi cards in euro, US cards in euro) is reported as such; the
84
+ wallet currency is inferred, never assumed from BIN country.
85
+ - Streams feed step 2: run exact currency-amount-pair clustering inside a
86
+ stream and use the stream, not the whole descriptor, as the baseline
87
+ population when a descriptor carries several streams.
88
+ - Do not call a frozen-rate stream laundering or fraud in the report; call it
89
+ generated traffic and show the drift comparison against live groups on the
90
+ same rungs and days.
@@ -4,13 +4,14 @@ Use these terms in TLD reports. Read before writing. After writing, run
4
4
  the proofread in `SKILL.md`.
5
5
 
6
6
  TLD in this skill is **transaction laundering**. Spell it out on first
7
- use. It is not a top-level domain.
7
+ use as **transaction laundering (TLD)**. The English report title is the first
8
+ use and must carry that expansion. It is not a top-level domain.
8
9
 
9
10
  ## Canonical glossary
10
11
 
11
12
  | English | Chinese | Do not write |
12
13
  |---|---|---|
13
- | TLD (transaction laundering) | 交易清洗 / TLD | 把 TLD 写成域名;洗钱结案 |
14
+ | transaction laundering (TLD) | 交易清洗(TLD | 把 TLD 写成域名;洗钱结案 |
14
15
  | potential TLD currency-amount pair | 潜在 TLD 币种金额对 | 已确认 TLD;TLD 商户 |
15
16
  | cluster (report section) | 币种金额对 | 客户簇;客群;分群结果 |
16
17
  | anchor detection | 锚点识别 | 客户聚类(对业务读者) |
@@ -31,13 +32,19 @@ use. It is not a top-level domain.
31
32
  | BIN country / issuer country | 发卡行国家 / BIN 国家 | 客户国家;收货国;发卡国 |
32
33
  | cluster composition by BIN country | BIN 国家占 Cluster | 客户国家占比 |
33
34
  | descriptor-country slice coverage | Cluster 占 Descriptor 对应 BIN 国家 | BIN 国家风险率;国家命中率 |
34
- | exact single-country standout | 100% 单一 BIN 国家 standout | 高国家占比;接近 100% |
35
+ | canonical exact single-country standout | canonical 原币 100% 单一 BIN 国家 standout | 高国家占比;接近 100% |
35
36
  | BIN-country local hour | BIN 国家当地小时 | 客户所在地时间;统一美国东部时间 |
36
37
  | this analysis window | 当前分析时段内 | 本窗口 |
37
38
  | MCC vs descriptor | MCC 与 descriptor 合理性 | 纠正 MCC;改 MCC |
38
39
  | verdict | 结论 | 长文综述(作结论部分时) |
39
40
  | High / Medium / Low risk | 高风险 / 中风险 / 低风险 | Supported;Weak;Ladder only;已确认 TLD |
40
41
  | risk exposure | 风险敞口 | 已确认 TLD 影响;定罪金额 |
42
+ | stream | 上游流 | 客群;渠道 |
43
+ | wallet currency (inferred) | 钱包币种(推断) | 发卡国货币(作为钱包币种时) |
44
+ | preset menu | 预设金额菜单 | 阶梯(指菜单时);手续费 |
45
+ | frozen-rate stream | 固定汇率流 | 机器人;已确认套现 |
46
+ | retail constant | 标价常数 | 税率;手续费率 |
47
+ | generated traffic | 生成流量 | 欺诈交易(作为结论时) |
41
48
 
42
49
  Keep proper nouns: Visa, MasterCard, BIN, IIN, MCC, MID, Worldpay,
43
50
  Pazien, Order ID, descriptor. Tables may show response codes; prose uses
@@ -45,9 +52,10 @@ Pazien, Order ID, descriptor. Tables may show response codes; prose uses
45
52
 
46
53
  ## Axis and UI
47
54
 
48
- - Amount axes name the **original** currency of that cluster
49
- (`授权尝试金额 USD`, `授权尝试金额 EUR`). Do not mix currencies on one
50
- axis.
55
+ - Canonical amount axes use and name the cluster's **original transaction
56
+ currency** (`授权尝试金额 USD`, `授权尝试金额 EUR`). FX amount axes use and
57
+ name the **target BIN-origin legal-tender currency**. Never mix currencies on
58
+ one axis in either view.
51
59
  - Date charts use each series' share of attempts by UTC transaction date.
52
60
  - Time charts use each BIN country's local hour, falling back to UTC when no
53
61
  timezone is configured. Normalize Cluster and Baseline independently.
@@ -59,7 +67,7 @@ Chinese: `走廊`, `杠杆`, `转化率`, `赋能`, `抓手`, `闭环`, `打法`
59
67
  `本窗口`, `已确认 TLD`, `已确认交易清洗`, `洗钱`, `发卡国`, `客群运营`,
60
68
  `客户簇`, `金额币种对`
61
69
 
62
- English: `confirmed laundering`, `confirmed TLD`, `money laundering`,
70
+ English: `deposit fee` or `surcharge` for a converted preset, `bot` as a finding, `confirmed laundering`, `confirmed TLD`, `money laundering`,
63
71
  `SAR`, `file a SAR`, `top-level domain`, `customer country`,
64
72
  `conversion rate` (when meaning auth rate), `corridor`, `price band`
65
73
  (as the cluster name)
@@ -71,7 +71,7 @@ is frozen.
71
71
 
72
72
  ## Order that must not move
73
73
 
74
- Clean → descriptor partition → currency-amount pairs → same-descriptor baseline → decide → report.
74
+ Clean → descriptor partition → stream inference (wallet currency, menu, frozen, retail) → currency-amount pairs inside streams → same-stream baseline (same-descriptor when one stream) → decide → report.
75
75
 
76
76
  Comparison features must not create a cluster that amount detection did not
77
77
  find. Decision does not add evidence; it only writes the ≤3-sentence