traderepublic-sync 0.3.1__tar.gz
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.
- traderepublic_sync-0.3.1/LICENSE +21 -0
- traderepublic_sync-0.3.1/PKG-INFO +373 -0
- traderepublic_sync-0.3.1/README.md +339 -0
- traderepublic_sync-0.3.1/pyproject.toml +56 -0
- traderepublic_sync-0.3.1/setup.cfg +4 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/__init__.py +60 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/_classify.py +101 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/client.py +940 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/constants.py +27 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/dual_legged/__init__.py +23 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/dual_legged/mapping.py +300 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/exceptions.py +51 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/parsing.py +235 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/py.typed +0 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/session.py +385 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/state.py +56 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync/waf.py +142 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync.egg-info/PKG-INFO +373 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync.egg-info/SOURCES.txt +25 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync.egg-info/dependency_links.txt +1 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync.egg-info/requires.txt +13 -0
- traderepublic_sync-0.3.1/src/traderepublic_sync.egg-info/top_level.txt +1 -0
- traderepublic_sync-0.3.1/tests/test_classify.py +267 -0
- traderepublic_sync-0.3.1/tests/test_dual_legged.py +270 -0
- traderepublic_sync-0.3.1/tests/test_fetch_filters.py +327 -0
- traderepublic_sync-0.3.1/tests/test_parsing.py +53 -0
- traderepublic_sync-0.3.1/tests/test_parsing_fixtures.py +102 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Helios de Creisquer
|
|
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.
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: traderepublic-sync
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Unofficial Trade Republic sync library: WAF acquisition, login + 2FA, WebSocket data fetching, transaction parsing.
|
|
5
|
+
Author-email: Helios de Creisquer <hdecreis@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hdecreis/libtrsync
|
|
8
|
+
Project-URL: Source, https://github.com/hdecreis/libtrsync
|
|
9
|
+
Project-URL: Issues, https://github.com/hdecreis/libtrsync/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/hdecreis/libtrsync/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: trade-republic,traderepublic,finance,broker,sync,scraping
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: websockets<17,>=15
|
|
24
|
+
Requires-Dist: requests<3,>=2.31
|
|
25
|
+
Provides-Extra: playwright
|
|
26
|
+
Requires-Dist: playwright>=1.52; extra == "playwright"
|
|
27
|
+
Provides-Extra: selenium
|
|
28
|
+
Requires-Dist: selenium>=4; extra == "selenium"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# traderepublic-sync
|
|
36
|
+
|
|
37
|
+
[](https://github.com/hdecreis/libtrsync/actions/workflows/ci.yml)
|
|
38
|
+
|
|
39
|
+
Unofficial Python client for **Trade Republic**. Handles AWS WAF token
|
|
40
|
+
acquisition, phone+PIN login with 2FA, WebSocket data fetching, and parsing
|
|
41
|
+
of the timeline-detail responses into structured Python dicts.
|
|
42
|
+
|
|
43
|
+
> ⚠️ **Unofficial.** Trade Republic does not publish an API. This library
|
|
44
|
+
> reverse-engineers the web app's WebSocket protocol; it can break at any
|
|
45
|
+
> time and is not endorsed by Trade Republic. Use at your own risk.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Base install (websockets + requests only)
|
|
51
|
+
pip install -e .
|
|
52
|
+
|
|
53
|
+
# With Playwright for WAF token acquisition (recommended)
|
|
54
|
+
pip install -e .[playwright]
|
|
55
|
+
playwright install chromium
|
|
56
|
+
|
|
57
|
+
# Or with Selenium
|
|
58
|
+
pip install -e .[selenium]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Python ≥ 3.11.
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import asyncio
|
|
67
|
+
from traderepublic_sync import TRClient
|
|
68
|
+
|
|
69
|
+
client = TRClient(locale="fr")
|
|
70
|
+
|
|
71
|
+
# 1. WAF token (uses headless browser)
|
|
72
|
+
client.acquire_waf_token("playwright")
|
|
73
|
+
|
|
74
|
+
# 2. Login - Trade Republic will push a 2FA prompt to your phone
|
|
75
|
+
login = client.login(phone_number="+33612121212", pin="1234")
|
|
76
|
+
print(f"2FA code requested. You have {login['countdown']}s.")
|
|
77
|
+
|
|
78
|
+
# Optional: ask for SMS instead of the in-app push
|
|
79
|
+
# client.request_sms(login["process_id"])
|
|
80
|
+
|
|
81
|
+
# 3. Verify 2FA (read the code from wherever the user enters it)
|
|
82
|
+
code = input("2FA code: ")
|
|
83
|
+
session_token = client.verify_2fa(login["process_id"], code)
|
|
84
|
+
|
|
85
|
+
# 4. Fetch data
|
|
86
|
+
result = asyncio.run(client.fetch_transactions(session_token))
|
|
87
|
+
print(f"{len(result['transactions'])} transactions, "
|
|
88
|
+
f"{len(result['raw_items'])} raw items")
|
|
89
|
+
|
|
90
|
+
balance = asyncio.run(client.fetch_cash_balance(session_token))
|
|
91
|
+
print("Cash:", balance)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Persisting session state
|
|
95
|
+
|
|
96
|
+
`ConnectionState` is a plain dataclass - pickle it, JSON-encode it, or store
|
|
97
|
+
it in your own DB. The WAF token + session token can be reused across
|
|
98
|
+
processes until they expire (typically a few hours).
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from dataclasses import asdict
|
|
102
|
+
import json
|
|
103
|
+
from traderepublic_sync import ConnectionState, TRClient
|
|
104
|
+
|
|
105
|
+
# Save after a successful login
|
|
106
|
+
state = ConnectionState(
|
|
107
|
+
phone_number="+33612121212",
|
|
108
|
+
pin="1234",
|
|
109
|
+
waf_token=client.waf_token,
|
|
110
|
+
device_info=client.device_info,
|
|
111
|
+
session_token=session_token,
|
|
112
|
+
auth_status="authenticated",
|
|
113
|
+
)
|
|
114
|
+
with open("tr_state.json", "w") as f:
|
|
115
|
+
json.dump(asdict(state), f)
|
|
116
|
+
|
|
117
|
+
# Restore later
|
|
118
|
+
with open("tr_state.json") as f:
|
|
119
|
+
state = ConnectionState(**json.load(f))
|
|
120
|
+
|
|
121
|
+
client = TRClient(waf_token=state.waf_token, device_info=state.device_info)
|
|
122
|
+
asyncio.run(client.fetch_transactions(state.session_token))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Dual-legged transactions (optional)
|
|
126
|
+
|
|
127
|
+
The mapping layer shapes TR events into a double-entry transaction schema
|
|
128
|
+
(PURCHASE / SELL / DIVIDEND / TRANSFER / …) with explicit credit / debit /
|
|
129
|
+
fee / tax legs. It lives in a separate submodule so generic users can ignore it:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from traderepublic_sync.dual_legged import (
|
|
133
|
+
build_dual_legged_transaction,
|
|
134
|
+
deduplicate_pea,
|
|
135
|
+
EVENT_TYPE_MAP,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Given a raw TR item + its parsed detail (use parse_detail_sections from
|
|
139
|
+
# the main package), produce a dual-legged transaction dict:
|
|
140
|
+
tx = build_dual_legged_transaction(raw_item, parsed_detail)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`fetch_transactions()` already applies this mapping and returns both forms
|
|
144
|
+
under `"transactions"` (dual-legged) and `"raw_items"` (raw TR items with the
|
|
145
|
+
parsed detail attached as `_detail` / `_detail_raw`).
|
|
146
|
+
|
|
147
|
+
## Live subscriptions (TRSession)
|
|
148
|
+
|
|
149
|
+
`TRClient` exposes one-shot helpers (`fetch_transactions`, `fetch_cash_balance`,
|
|
150
|
+
`fetch_ticker`, …) that open a WebSocket, send a single request, and close.
|
|
151
|
+
For **streaming** use cases — live ticker, live portfolio updates, instrument
|
|
152
|
+
search — open a long-lived session via `client.open_session()`.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import asyncio
|
|
156
|
+
from traderepublic_sync import TRClient
|
|
157
|
+
|
|
158
|
+
client = TRClient(waf_token=..., device_info=...)
|
|
159
|
+
# ...login + verify_2fa as in the quickstart...
|
|
160
|
+
|
|
161
|
+
async def watch_apple():
|
|
162
|
+
async with client.open_session(session_token) as session:
|
|
163
|
+
def on_tick(data):
|
|
164
|
+
last = (data.get("last") or {}).get("price")
|
|
165
|
+
print(f"AAPL = {last}")
|
|
166
|
+
|
|
167
|
+
sub_id = await session.subscribe_ticker("US0378331005", on_tick)
|
|
168
|
+
await asyncio.sleep(60) # stream for a minute
|
|
169
|
+
await session.unsubscribe(sub_id) # optional — __aexit__ also cleans up
|
|
170
|
+
|
|
171
|
+
asyncio.run(watch_apple())
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Convenience subscriptions
|
|
175
|
+
|
|
176
|
+
| Method | What it streams |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `subscribe_ticker(isin, cb)` | Live price (`last`, `bid`, `ask`, `open`, `pre`) — resolves the home exchange for you |
|
|
179
|
+
| `subscribe_portfolio(sec_acc_no, cb)` | Live positions list (quantity + cost basis) |
|
|
180
|
+
| `subscribe_cash(cash_acc_no, cb)` | Available cash balance |
|
|
181
|
+
| `subscribe_transactions(cash_acc_no, cb)` | Timeline transactions as they appear |
|
|
182
|
+
|
|
183
|
+
All callbacks may be plain functions or coroutines. They receive the parsed
|
|
184
|
+
JSON payload of each incoming frame; errors in the callback are logged and
|
|
185
|
+
swallowed so one bad frame doesn't kill the stream.
|
|
186
|
+
|
|
187
|
+
### Searching for instruments (securities)
|
|
188
|
+
|
|
189
|
+
`search_instrument(query, instrument_type=None, limit=20)` queries TR's
|
|
190
|
+
`neonSearch` endpoint — the same one the web app uses for the asset
|
|
191
|
+
picker. It accepts a free-text query (name, ticker, ISIN fragment) and
|
|
192
|
+
returns the raw result list.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
async with client.open_session(session_token) as session:
|
|
196
|
+
# By asset class
|
|
197
|
+
btc = await session.search_instrument("bitcoin", instrument_type="crypto")
|
|
198
|
+
apple = await session.search_instrument("apple", instrument_type="stock")
|
|
199
|
+
msci = await session.search_instrument("MSCI World", instrument_type="etf")
|
|
200
|
+
|
|
201
|
+
# Without a type filter, results span all asset classes
|
|
202
|
+
mixed = await session.search_instrument("tesla", limit=5)
|
|
203
|
+
|
|
204
|
+
# By ISIN (or fragment)
|
|
205
|
+
by_isin = await session.search_instrument("US0378331005")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Parameters**
|
|
209
|
+
|
|
210
|
+
| Name | Type | Notes |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `query` | `str` | Free-text search — name, ticker, partial ISIN |
|
|
213
|
+
| `instrument_type` | `str \| None` | One of `"crypto"`, `"stock"`, `"etf"`, `"bond"`, `"derivative"`, `"fund"` — or `None` to search everything |
|
|
214
|
+
| `limit` | `int` | Max results (default `20`) |
|
|
215
|
+
|
|
216
|
+
**Result shape** — each item is a raw TR dict, typically including:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
{
|
|
220
|
+
"isin": "XF000BTC0017", # ISIN or pseudo-ISIN for crypto
|
|
221
|
+
"name": "Bitcoin",
|
|
222
|
+
"type": "crypto",
|
|
223
|
+
"exchanges": [{"slug": "BTC", "name": "Bitcoin"}],
|
|
224
|
+
# ...additional fields vary by asset class
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Use the returned `isin` to feed `subscribe_ticker()`, `fetch_ticker()`,
|
|
229
|
+
or any other instrument-keyed API on the client.
|
|
230
|
+
|
|
231
|
+
### Lower-level primitives
|
|
232
|
+
|
|
233
|
+
If a TR subscription type isn't covered by the convenience helpers, use
|
|
234
|
+
`subscribe()` / `request()` directly:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
async with client.open_session(session_token) as session:
|
|
238
|
+
# One-shot: subscribe, take the first frame, unsubscribe.
|
|
239
|
+
data = await session.request("availableCash", {"id": cash_acc_no})
|
|
240
|
+
|
|
241
|
+
# Streaming: keep receiving until you unsubscribe.
|
|
242
|
+
sub_id = await session.subscribe(
|
|
243
|
+
"compactPortfolio",
|
|
244
|
+
{"secAccNo": sec_acc_no},
|
|
245
|
+
callback=lambda d: print(d["positions"]),
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The WebSocket token is injected for you; you don't need to pass it in
|
|
250
|
+
`params`.
|
|
251
|
+
|
|
252
|
+
## Downloading files listed in transactions
|
|
253
|
+
|
|
254
|
+
The key points:
|
|
255
|
+
- Cookie: tr_session=<session_token> — this is how TR authenticates document downloads (same mechanism as login).
|
|
256
|
+
- Headers: reuse client._headers() for x-aws-waf-token and x-tr-device-info — TR's WAF will reject requests without a valid token.
|
|
257
|
+
- The document URLs are absolute https:// URLs already, no base URL manipulation needed.
|
|
258
|
+
|
|
259
|
+
Examples:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
import requests
|
|
263
|
+
|
|
264
|
+
def download_tr_documents(client, session_token: str, transactions: list, output_dir: str = "."):
|
|
265
|
+
"""Download all PDF documents from a list of dual-legged transactions."""
|
|
266
|
+
import os
|
|
267
|
+
|
|
268
|
+
headers = client._headers() # includes x-aws-waf-token + x-tr-device-info
|
|
269
|
+
cookies = {"tr_session": session_token}
|
|
270
|
+
|
|
271
|
+
for tx in transactions:
|
|
272
|
+
for doc in tx.get("document_urls", []):
|
|
273
|
+
url = doc["url"]
|
|
274
|
+
title = doc["title"].replace("/", "-")
|
|
275
|
+
tr_id = tx.get("tr_id", "unknown")
|
|
276
|
+
filename = f"{tx['date'][:10]}_{tr_id}_{title}.pdf"
|
|
277
|
+
filepath = os.path.join(output_dir, filename)
|
|
278
|
+
|
|
279
|
+
resp = requests.get(url, headers=headers, cookies=cookies)
|
|
280
|
+
resp.raise_for_status()
|
|
281
|
+
|
|
282
|
+
with open(filepath, "wb") as f:
|
|
283
|
+
f.write(resp.content)
|
|
284
|
+
print(f"Saved: {filepath}")
|
|
285
|
+
|
|
286
|
+
result = asyncio.run(client.fetch_transactions(session_token))
|
|
287
|
+
download_tr_documents(client, session_token, result["transactions"], output_dir="/tmp/tr_docs")
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Or if you want to pull from raw_items instead (same document URLs, accessible before the dual-legged mapping):
|
|
291
|
+
```
|
|
292
|
+
for item in result["raw_items"]:
|
|
293
|
+
for doc in item["_detail"].get("document_urls", []):
|
|
294
|
+
...
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Pure parsing utilities
|
|
298
|
+
|
|
299
|
+
These are dependency-free helpers you can use without authenticating:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from traderepublic_sync import (
|
|
303
|
+
parse_currency_amount, # "1 000,00 EUR" → 1000.0
|
|
304
|
+
parse_detail_sections, # timelineDetailV2 dict → structured dict
|
|
305
|
+
extract_isin_from_icon, # "logos/FR0011550672/v2" → "FR0011550672"
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Layout
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
src/traderepublic_sync/
|
|
313
|
+
├── client.py # TRClient (login, 2FA, one-shot websocket fetches)
|
|
314
|
+
├── session.py # TRSession (long-lived ws, callback-based subscriptions)
|
|
315
|
+
├── waf.py # AWS WAF token via Playwright or Selenium
|
|
316
|
+
├── parsing.py # parse_currency_amount, parse_detail_sections, ISIN extraction
|
|
317
|
+
├── state.py # ConnectionState dataclass
|
|
318
|
+
├── constants.py # API URLs, default headers, WS connect payload
|
|
319
|
+
├── exceptions.py # TRAuthError
|
|
320
|
+
└── dual_legged/ # Optional dual-legged transaction mapping
|
|
321
|
+
└── mapping.py
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Reporting bugs
|
|
325
|
+
|
|
326
|
+
The TR API is undocumented and locale-sensitive — the most useful thing
|
|
327
|
+
you can attach to a bug report is a redacted copy of your own dump, so we
|
|
328
|
+
can reproduce the parsing path that misbehaved. Two scripts make this
|
|
329
|
+
safe:
|
|
330
|
+
|
|
331
|
+
1. **Dump everything** with `examples/smoke_fetch_all.py` — writes
|
|
332
|
+
`accounts.json`, `assets.json`, `transactions_raw.json` and
|
|
333
|
+
`transactions_dl.json` under `examples/out/`. The raw file contains
|
|
334
|
+
the full TR responses (`_detail` + `_detail_raw`) which is what we
|
|
335
|
+
need for parser fixes.
|
|
336
|
+
2. **Anonymize** the whole folder with `scripts/redact_dump.py` before
|
|
337
|
+
sharing. It keeps every item and preserves the data shape, but:
|
|
338
|
+
- replaces ``sender`` / ``iban`` / ``holderName`` / ``email`` /
|
|
339
|
+
``phone`` field values wholesale,
|
|
340
|
+
- regex-scrubs IBANs, JWTs, emails, phone numbers, AWS pre-signed
|
|
341
|
+
URL query strings, and any 10+ digit run,
|
|
342
|
+
- maps TR cash account numbers to consistent placeholders
|
|
343
|
+
(``9000000001``, ``9000000002``, …) so cross-file references still
|
|
344
|
+
match,
|
|
345
|
+
- takes ``--also-redact "<string>"`` for anything the regexes can't
|
|
346
|
+
infer (your real name and its variants, account labels, etc.).
|
|
347
|
+
Matched case-insensitively. Repeat the flag per term.
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# Default in/out: examples/out/ → examples/out_redacted/
|
|
351
|
+
python scripts/redact_dump.py \
|
|
352
|
+
--also-redact "Jane Doe" \
|
|
353
|
+
--also-redact "Jane-Doe" \
|
|
354
|
+
--also-redact "DOE-J"
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The script prints a per-rule hit count and the cash-account-number
|
|
358
|
+
mapping at the end. **Open the redacted JSONs and search for any
|
|
359
|
+
remaining real names, IBAN fragments, or labels** — name variants
|
|
360
|
+
(truncations, capitalizations, hyphenations) are the classic blind
|
|
361
|
+
spot. Re-run with extra ``--also-redact`` flags until clean.
|
|
362
|
+
|
|
363
|
+
3. **Open an issue** on
|
|
364
|
+
[github.com/hdecreis/libtrsync](https://github.com/hdecreis/libtrsync/issues)
|
|
365
|
+
describing what you expected vs. what happened, and attach the
|
|
366
|
+
relevant file(s) from `examples/out_redacted/`. A single offending
|
|
367
|
+
`eventType` is often enough — pulling one item with
|
|
368
|
+
`scripts/extract_fixture.py` (which also sanitizes) lets us turn it
|
|
369
|
+
straight into a regression test.
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT. See [LICENSE](LICENSE).
|