syntheticcfo 0.1.0__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.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: syntheticcfo
3
+ Version: 0.1.0
4
+ Summary: Python client for synthetic cfo: forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud) with a labelled fraud answer key.
5
+ Author: synthetic cfo
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://app.syntheticcfo.com
8
+ Project-URL: Documentation, https://app.syntheticcfo.com/evaluate
9
+ Keywords: synthetic data,erp,sap,oracle,fraud detection,audit,benchmark
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # syntheticcfo - Python client
19
+
20
+ Generate forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud)
21
+ from a script or notebook. Every dataset ships with a complete labelled fraud
22
+ answer key, articulated financial statements and a reproducibility certificate:
23
+ the same seed and configuration reproduce the package byte for byte. No
24
+ language model ever generates the numbers.
25
+
26
+ The client is one file with no dependencies beyond the standard library. Use it
27
+ directly:
28
+
29
+ ```bash
30
+ curl -O https://app.syntheticcfo.com/sdk/syntheticcfo.py
31
+ ```
32
+
33
+ or install it as a package from this directory:
34
+
35
+ ```bash
36
+ pip install .
37
+ ```
38
+
39
+ ## Quickstart
40
+
41
+ Create an API key at https://app.syntheticcfo.com under Account, API access.
42
+ The key carries your plan and monthly allowance. It cannot manage your account,
43
+ read your chats or create more keys.
44
+
45
+ ```python
46
+ from syntheticcfo import Client
47
+
48
+ c = Client(api_key="scfo_...") # or set the SCFO_API_KEY env var
49
+ print(c.me()["plan"]) # your plan and remaining generations
50
+
51
+ job = c.generate(
52
+ platform="ORACLE", # SAP_ECC | SAP_S4HANA | ORACLE
53
+ industry="RETAIL", # TMT PHARMA MANUFACTURING FINANCIAL_SERVICES RETAIL
54
+ fraud="high", # none | low | medium | high
55
+ rows=600,
56
+ modules=["P2P", "O2C"], # subset of P2P O2C CE R2R HR MM FA
57
+ jurisdiction="ZA",
58
+ seed=42,
59
+ wait=True, # poll until the package is built
60
+ )
61
+
62
+ package = job.download("out/") # the four-layer audit package (zip)
63
+ lab = job.lab_export("out/") # JSONL tables + answer_key.jsonl + dataset card
64
+ print(job.insights()) # what was planted, by control
65
+ ```
66
+
67
+ For model evaluation, add `evaluation_mode=True`: the shipped database then
68
+ withholds the ground-truth marker columns so nothing in the data betrays the
69
+ answer key, while the labels still ship separately in `01_Audit_Evidence`.
70
+
71
+ ## Notes
72
+
73
+ - Plan limits apply exactly as in the app: over-plan requests are refused with
74
+ an honest message, and row counts above your plan are clamped with a note in
75
+ the package, never silently.
76
+ - A failed or interrupted generation refunds the metered credit automatically.
77
+ - Free-plan datasets and the public sample may not be redistributed, resold, or
78
+ used to train or evaluate a competing product (see the Terms in the app).
@@ -0,0 +1,61 @@
1
+ # syntheticcfo - Python client
2
+
3
+ Generate forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud)
4
+ from a script or notebook. Every dataset ships with a complete labelled fraud
5
+ answer key, articulated financial statements and a reproducibility certificate:
6
+ the same seed and configuration reproduce the package byte for byte. No
7
+ language model ever generates the numbers.
8
+
9
+ The client is one file with no dependencies beyond the standard library. Use it
10
+ directly:
11
+
12
+ ```bash
13
+ curl -O https://app.syntheticcfo.com/sdk/syntheticcfo.py
14
+ ```
15
+
16
+ or install it as a package from this directory:
17
+
18
+ ```bash
19
+ pip install .
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ Create an API key at https://app.syntheticcfo.com under Account, API access.
25
+ The key carries your plan and monthly allowance. It cannot manage your account,
26
+ read your chats or create more keys.
27
+
28
+ ```python
29
+ from syntheticcfo import Client
30
+
31
+ c = Client(api_key="scfo_...") # or set the SCFO_API_KEY env var
32
+ print(c.me()["plan"]) # your plan and remaining generations
33
+
34
+ job = c.generate(
35
+ platform="ORACLE", # SAP_ECC | SAP_S4HANA | ORACLE
36
+ industry="RETAIL", # TMT PHARMA MANUFACTURING FINANCIAL_SERVICES RETAIL
37
+ fraud="high", # none | low | medium | high
38
+ rows=600,
39
+ modules=["P2P", "O2C"], # subset of P2P O2C CE R2R HR MM FA
40
+ jurisdiction="ZA",
41
+ seed=42,
42
+ wait=True, # poll until the package is built
43
+ )
44
+
45
+ package = job.download("out/") # the four-layer audit package (zip)
46
+ lab = job.lab_export("out/") # JSONL tables + answer_key.jsonl + dataset card
47
+ print(job.insights()) # what was planted, by control
48
+ ```
49
+
50
+ For model evaluation, add `evaluation_mode=True`: the shipped database then
51
+ withholds the ground-truth marker columns so nothing in the data betrays the
52
+ answer key, while the labels still ship separately in `01_Audit_Evidence`.
53
+
54
+ ## Notes
55
+
56
+ - Plan limits apply exactly as in the app: over-plan requests are refused with
57
+ an honest message, and row counts above your plan are clamped with a note in
58
+ the package, never silently.
59
+ - A failed or interrupted generation refunds the metered credit automatically.
60
+ - Free-plan datasets and the public sample may not be redistributed, resold, or
61
+ used to train or evaluate a competing product (see the Terms in the app).
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "syntheticcfo"
7
+ version = "0.1.0"
8
+ description = "Python client for synthetic cfo: forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud) with a labelled fraud answer key."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "Proprietary" }
12
+ authors = [{ name = "synthetic cfo" }]
13
+ keywords = ["synthetic data", "erp", "sap", "oracle", "fraud detection", "audit", "benchmark"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Financial and Insurance Industry",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Office/Business :: Financial :: Accounting",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://app.syntheticcfo.com"
24
+ Documentation = "https://app.syntheticcfo.com/evaluate"
25
+
26
+ [tool.setuptools]
27
+ py-modules = ["syntheticcfo"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: syntheticcfo
3
+ Version: 0.1.0
4
+ Summary: Python client for synthetic cfo: forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud) with a labelled fraud answer key.
5
+ Author: synthetic cfo
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://app.syntheticcfo.com
8
+ Project-URL: Documentation, https://app.syntheticcfo.com/evaluate
9
+ Keywords: synthetic data,erp,sap,oracle,fraud detection,audit,benchmark
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # syntheticcfo - Python client
19
+
20
+ Generate forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud)
21
+ from a script or notebook. Every dataset ships with a complete labelled fraud
22
+ answer key, articulated financial statements and a reproducibility certificate:
23
+ the same seed and configuration reproduce the package byte for byte. No
24
+ language model ever generates the numbers.
25
+
26
+ The client is one file with no dependencies beyond the standard library. Use it
27
+ directly:
28
+
29
+ ```bash
30
+ curl -O https://app.syntheticcfo.com/sdk/syntheticcfo.py
31
+ ```
32
+
33
+ or install it as a package from this directory:
34
+
35
+ ```bash
36
+ pip install .
37
+ ```
38
+
39
+ ## Quickstart
40
+
41
+ Create an API key at https://app.syntheticcfo.com under Account, API access.
42
+ The key carries your plan and monthly allowance. It cannot manage your account,
43
+ read your chats or create more keys.
44
+
45
+ ```python
46
+ from syntheticcfo import Client
47
+
48
+ c = Client(api_key="scfo_...") # or set the SCFO_API_KEY env var
49
+ print(c.me()["plan"]) # your plan and remaining generations
50
+
51
+ job = c.generate(
52
+ platform="ORACLE", # SAP_ECC | SAP_S4HANA | ORACLE
53
+ industry="RETAIL", # TMT PHARMA MANUFACTURING FINANCIAL_SERVICES RETAIL
54
+ fraud="high", # none | low | medium | high
55
+ rows=600,
56
+ modules=["P2P", "O2C"], # subset of P2P O2C CE R2R HR MM FA
57
+ jurisdiction="ZA",
58
+ seed=42,
59
+ wait=True, # poll until the package is built
60
+ )
61
+
62
+ package = job.download("out/") # the four-layer audit package (zip)
63
+ lab = job.lab_export("out/") # JSONL tables + answer_key.jsonl + dataset card
64
+ print(job.insights()) # what was planted, by control
65
+ ```
66
+
67
+ For model evaluation, add `evaluation_mode=True`: the shipped database then
68
+ withholds the ground-truth marker columns so nothing in the data betrays the
69
+ answer key, while the labels still ship separately in `01_Audit_Evidence`.
70
+
71
+ ## Notes
72
+
73
+ - Plan limits apply exactly as in the app: over-plan requests are refused with
74
+ an honest message, and row counts above your plan are clamped with a note in
75
+ the package, never silently.
76
+ - A failed or interrupted generation refunds the metered credit automatically.
77
+ - Free-plan datasets and the public sample may not be redistributed, resold, or
78
+ used to train or evaluate a competing product (see the Terms in the app).
@@ -0,0 +1,7 @@
1
+ README.md
2
+ pyproject.toml
3
+ syntheticcfo.py
4
+ syntheticcfo.egg-info/PKG-INFO
5
+ syntheticcfo.egg-info/SOURCES.txt
6
+ syntheticcfo.egg-info/dependency_links.txt
7
+ syntheticcfo.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ syntheticcfo
@@ -0,0 +1,278 @@
1
+ """synthetic cfo - Python client.
2
+
3
+ Generate forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud)
4
+ from a script or notebook. One file, standard library only - copy it anywhere.
5
+
6
+ Quickstart:
7
+
8
+ from syntheticcfo import Client
9
+
10
+ c = Client(api_key="scfo_...") # or set the SCFO_API_KEY env var
11
+ job = c.generate(platform="ORACLE", industry="RETAIL", fraud="high",
12
+ rows=600, modules=["P2P", "O2C"], jurisdiction="ZA",
13
+ seed=42, wait=True)
14
+ package = job.download("out/") # four-layer audit package (zip)
15
+ lab = job.lab_export("out/") # JSONL tables + answer key + dataset card
16
+
17
+ Create an API key on your account page (Account -> API access) at
18
+ https://app.syntheticcfo.com. The key carries your plan and monthly allowance;
19
+ it cannot manage the account itself.
20
+
21
+ Every dataset is deterministic: the same seed and configuration reproduce the
22
+ package byte for byte, and the reproducibility certificate in the package
23
+ proves it. No language model ever generates the numbers.
24
+ """
25
+
26
+ import json as _json
27
+ import os as _os
28
+ import re as _re
29
+ import ssl as _ssl
30
+ import time as _time
31
+ import urllib.error as _uerr
32
+ import urllib.request as _ureq
33
+ from pathlib import Path as _Path
34
+
35
+ __version__ = "0.1.0"
36
+ __all__ = ["Client", "Job", "SyntheticCfoError"]
37
+
38
+ _DEFAULT_BASE = "https://app.syntheticcfo.com"
39
+
40
+ # The vocabulary the server validates against (engine/interpreter.py ALLOWED).
41
+ # The server is the authority; these are listed for discoverability.
42
+ PLATFORMS = ["SAP_ECC", "SAP_S4HANA", "ORACLE"]
43
+ MODULES = ["P2P", "O2C", "CE", "R2R", "HR", "MM", "FA"]
44
+ INDUSTRIES = ["TMT", "PHARMA", "MANUFACTURING", "FINANCIAL_SERVICES", "RETAIL"]
45
+ FRAUD_LEVELS = ["none", "low", "medium", "high"]
46
+
47
+ _PLATFORM_ALIASES = {
48
+ "sap": "SAP_ECC", "ecc": "SAP_ECC", "sap ecc": "SAP_ECC", "sap_ecc": "SAP_ECC",
49
+ "s4": "SAP_S4HANA", "s4hana": "SAP_S4HANA", "s/4hana": "SAP_S4HANA",
50
+ "sap_s4hana": "SAP_S4HANA", "hana": "SAP_S4HANA",
51
+ "oracle": "ORACLE", "oracle cloud": "ORACLE", "oracle_cloud": "ORACLE",
52
+ "fusion": "ORACLE",
53
+ }
54
+
55
+
56
+ class SyntheticCfoError(Exception):
57
+ """An error answered by the platform. .status carries the HTTP code."""
58
+
59
+ def __init__(self, message, status=None):
60
+ super().__init__(message)
61
+ self.status = status
62
+
63
+
64
+ class Client:
65
+ """A synthetic cfo account, spoken to over its API key.
66
+
67
+ api_key your key from Account -> API access (falls back to SCFO_API_KEY)
68
+ base_url the platform (defaults to the hosted app)
69
+ timeout per-request timeout in seconds
70
+ """
71
+
72
+ def __init__(self, api_key=None, base_url=None, timeout=120):
73
+ self.api_key = (api_key or _os.environ.get("SCFO_API_KEY", "")).strip()
74
+ if not self.api_key:
75
+ raise SyntheticCfoError(
76
+ "No API key. Pass Client(api_key=...) or set SCFO_API_KEY. "
77
+ "Create one at https://app.syntheticcfo.com (Account -> API access).")
78
+ self.base_url = (base_url or _os.environ.get("SCFO_BASE_URL", "") or
79
+ _DEFAULT_BASE).rstrip("/")
80
+ self.timeout = timeout
81
+
82
+ # -- plumbing ---------------------------------------------------------------
83
+ def _req(self, method, path, body=None):
84
+ url = self.base_url + path
85
+ data = _json.dumps(body).encode() if body is not None else None
86
+ req = _ureq.Request(url, data=data, method=method)
87
+ req.add_header("Authorization", "Bearer " + self.api_key)
88
+ req.add_header("Accept", "application/json")
89
+ req.add_header("User-Agent", "syntheticcfo-python/" + __version__)
90
+ if data is not None:
91
+ req.add_header("Content-Type", "application/json")
92
+ try:
93
+ with _ureq.urlopen(req, timeout=self.timeout,
94
+ context=_ssl.create_default_context()) as r:
95
+ return _json.loads(r.read().decode() or "{}")
96
+ except _uerr.HTTPError as e:
97
+ raise SyntheticCfoError(self._err_message(e), status=e.code) from None
98
+ except _uerr.URLError as e:
99
+ raise SyntheticCfoError(
100
+ "Cannot reach %s (%s). Check your connection." % (self.base_url,
101
+ e.reason)) from None
102
+
103
+ def _err_message(self, e):
104
+ try:
105
+ detail = _json.loads(e.read().decode()).get("detail", "")
106
+ except Exception:
107
+ detail = ""
108
+ if e.code == 401:
109
+ return detail or ("Not authorised. Check your API key, or create a new one "
110
+ "at Account -> API access.")
111
+ if e.code == 402:
112
+ return (detail or "Your plan does not cover this request.") + \
113
+ " Upgrade at https://app.syntheticcfo.com/request-access."
114
+ return detail or ("Request failed with HTTP %d." % e.code)
115
+
116
+ def _save(self, path, dest, fallback_name):
117
+ """Stream an authenticated download to dest (a directory or file path)."""
118
+ url = self.base_url + path
119
+ req = _ureq.Request(url)
120
+ req.add_header("Authorization", "Bearer " + self.api_key)
121
+ req.add_header("User-Agent", "syntheticcfo-python/" + __version__)
122
+ try:
123
+ r = _ureq.urlopen(req, timeout=max(self.timeout, 600),
124
+ context=_ssl.create_default_context())
125
+ except _uerr.HTTPError as e:
126
+ raise SyntheticCfoError(self._err_message(e), status=e.code) from None
127
+ with r:
128
+ name = fallback_name
129
+ cd = r.headers.get("Content-Disposition", "")
130
+ m = _re.search(r'filename="?([^";]+)"?', cd)
131
+ if m:
132
+ name = _Path(m.group(1)).name
133
+ dest = _Path(dest)
134
+ out = dest / name if (dest.is_dir() or not dest.suffix) else dest
135
+ out.parent.mkdir(parents=True, exist_ok=True)
136
+ with open(out, "wb") as f:
137
+ while True:
138
+ chunk = r.read(1 << 16)
139
+ if not chunk:
140
+ break
141
+ f.write(chunk)
142
+ return out
143
+
144
+ # -- account ----------------------------------------------------------------
145
+ def me(self):
146
+ """Your account: email, plan, monthly generations, capability caps."""
147
+ return self._req("GET", "/api/v1/auth/me")
148
+
149
+ # -- generation -------------------------------------------------------------
150
+ def generate(self, platform="SAP_ECC", modules=None, rows=None, industry="TMT",
151
+ fraud="medium", jurisdiction=None, region=None, seed=None,
152
+ years=1, entropy=0, blind=False, evaluation_mode=False,
153
+ size_band=None, fx_share=0.0, fiscal_year=2025,
154
+ wait=False, poll_interval=3.0, quiet=False):
155
+ """Start a generation and return the Job.
156
+
157
+ platform SAP_ECC | SAP_S4HANA | ORACLE (aliases like "oracle" work)
158
+ modules subset of P2P O2C CE R2R HR MM FA (default: the core four)
159
+ rows target rows per major table (plan caps apply, honestly clamped)
160
+ fraud none | low | medium | high
161
+ seed any integer; the same seed + config reproduces byte-identically
162
+ evaluation_mode withhold ground-truth marker columns from the shipped
163
+ database so a model cannot read the answer key (the labels
164
+ still ship in 01_Audit_Evidence)
165
+ wait block until the package is built, then return the Job
166
+ """
167
+ plat = _PLATFORM_ALIASES.get(str(platform).strip().lower(), platform)
168
+ body = {
169
+ "platform": plat,
170
+ "modules": list(modules) if modules else ["P2P", "O2C", "CE", "R2R"],
171
+ "num_rows": int(rows) if rows else None,
172
+ "industry": str(industry).upper(),
173
+ "fraud_intensity": str(fraud).lower(),
174
+ "fiscal_year": int(fiscal_year),
175
+ "seed": int(seed) if seed is not None else None,
176
+ "num_years": int(years),
177
+ "jurisdiction": jurisdiction,
178
+ "region": region,
179
+ "size_band": size_band,
180
+ "entropy_level": int(entropy),
181
+ "blind_track": 1 if blind else 0,
182
+ "foreign_currency_share": float(fx_share),
183
+ "evaluation_mode": bool(evaluation_mode),
184
+ }
185
+ d = self._req("POST", "/api/v1/jobs", body)
186
+ job = Job(self, d["job_id"])
187
+ if wait:
188
+ job.wait(poll_interval=poll_interval, quiet=quiet)
189
+ return job
190
+
191
+ def job(self, job_id):
192
+ """Attach to an existing job by id."""
193
+ j = Job(self, job_id)
194
+ j.refresh()
195
+ return j
196
+
197
+ def jobs(self):
198
+ """Your generation history, newest first (list of dicts)."""
199
+ d = self._req("GET", "/api/v1/jobs")
200
+ return d.get("jobs", d) if isinstance(d, dict) else d
201
+
202
+
203
+ class Job:
204
+ """One generation. Poll it, then download the package or the lab export."""
205
+
206
+ def __init__(self, client, job_id):
207
+ self._c = client
208
+ self.id = job_id
209
+ self._d = {}
210
+
211
+ def refresh(self):
212
+ self._d = self._c._req("GET", "/api/v1/jobs/" + self.id)
213
+ return self
214
+
215
+ @property
216
+ def status(self):
217
+ return self._d.get("status")
218
+
219
+ @property
220
+ def progress(self):
221
+ return self._d.get("progress") or 0
222
+
223
+ @property
224
+ def message(self):
225
+ return self._d.get("message") or ""
226
+
227
+ @property
228
+ def error(self):
229
+ return self._d.get("error") or ""
230
+
231
+ @property
232
+ def seed(self):
233
+ return self._d.get("seed")
234
+
235
+ def wait(self, timeout=3600, poll_interval=3.0, quiet=False):
236
+ """Poll until the job completes. Raises on failure or cancellation."""
237
+ start = _time.monotonic()
238
+ last = ""
239
+ while True:
240
+ self.refresh()
241
+ st = self.status
242
+ if not quiet:
243
+ line = "[%s] %s%% %s" % (st, self.progress, self.message)
244
+ if line != last:
245
+ print(line, flush=True)
246
+ last = line
247
+ if st == "complete":
248
+ return self
249
+ if st in ("failed", "cancelled"):
250
+ raise SyntheticCfoError(self.error or ("Job %s." % st))
251
+ if _time.monotonic() - start > timeout:
252
+ raise SyntheticCfoError(
253
+ "Timed out after %ds; the job is still %s. Attach again later "
254
+ "with client.job(%r)." % (timeout, st, self.id))
255
+ _time.sleep(poll_interval)
256
+
257
+ def download(self, dest="."):
258
+ """Save the four-layer audit package zip. Returns the saved Path."""
259
+ return self._c._save("/api/v1/jobs/%s/download" % self.id, dest,
260
+ "synthetic_cfo_%s.zip" % self.id[:8])
261
+
262
+ def lab_export(self, dest="."):
263
+ """Save the lab export zip: one JSONL per table, answer_key.jsonl,
264
+ DATASET_CARD.md, the reproducibility certificate and sha256 checksums."""
265
+ return self._c._save("/api/v1/jobs/%s/lab-export" % self.id, dest,
266
+ "synthetic_cfo_lab_%s.zip" % self.id[:8])
267
+
268
+ def insights(self):
269
+ """The dataset's insight summary (modules, fraud counts, key tables)."""
270
+ return self._c._req("GET", "/api/v1/jobs/%s/insights" % self.id)
271
+
272
+ def cancel(self):
273
+ """Stop a running generation. The credit is refunded on metered plans."""
274
+ return self._c._req("POST", "/api/v1/jobs/%s/cancel" % self.id)
275
+
276
+ def __repr__(self):
277
+ return "Job(%r, status=%r, progress=%s)" % (self.id, self.status,
278
+ self.progress)