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.
- syntheticcfo-0.1.0/PKG-INFO +78 -0
- syntheticcfo-0.1.0/README.md +61 -0
- syntheticcfo-0.1.0/pyproject.toml +27 -0
- syntheticcfo-0.1.0/setup.cfg +4 -0
- syntheticcfo-0.1.0/syntheticcfo.egg-info/PKG-INFO +78 -0
- syntheticcfo-0.1.0/syntheticcfo.egg-info/SOURCES.txt +7 -0
- syntheticcfo-0.1.0/syntheticcfo.egg-info/dependency_links.txt +1 -0
- syntheticcfo-0.1.0/syntheticcfo.egg-info/top_level.txt +1 -0
- syntheticcfo-0.1.0/syntheticcfo.py +278 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -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)
|