Metadata-Version: 2.4
Name: syntheticcfo
Version: 0.2.2
Summary: Python client for synthetic cfo: forensic-grade synthetic ERP data (SAP ECC, SAP S/4HANA, Oracle Cloud) with a labelled fraud answer key.
Author: synthetic cfo
License-Expression: MIT
Project-URL: Homepage, https://app.syntheticcfo.com
Project-URL: Documentation, https://app.syntheticcfo.com/evaluate
Project-URL: Source, https://github.com/syntheticcfo/syntheticcfo
Project-URL: Proof, https://app.syntheticcfo.com/proof
Keywords: synthetic data,erp,sap,oracle,fraud detection,audit,benchmark
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Accounting
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# syntheticcfo - Python client

synthetic cfo generates SAP and Oracle ledgers from accounting rules, with fraud
planted, every planted record written down at the moment it is planted, and
innocent look-alikes beside nearly every scheme, so fraud detection can be
trained and measured without a single real record. Three schemes are closed
sets by design, with no look-alike planted: segregation of duties, the ghost
vendor and phantom inventory, where the join or the reconciliation that finds
them is the real analytic and every match is a genuine exception.

This client generates that data, for SAP ECC, SAP S/4HANA and Oracle Cloud, from
a script, a notebook or one command. Every dataset it generates is a four-layer
package with its labelled fraud answer key (a blind-track dataset keeps part of the key
sealed on the platform for scoring) and a reproducibility certificate that names
the engine version and lists the SHA-256 of its files: the same seed and
configuration on the same engine version reproduce the package byte for byte,
and on another engine version they can build a different dataset. A dataset
with purchase to pay, order to cash, payroll, inventory or fixed assets also
carries articulated financial statements. No language model ever generates
the numbers.

The client has no dependencies beyond the standard library:

```bash
pip install syntheticcfo
```

That installs the client and the `syntheticcfo` command. If your network blocks
the package index, the platform serves the current wheel itself; the exact
command is on https://app.syntheticcfo.com/evaluate.

Or, since it is one file, take it directly and drop it anywhere:

```bash
curl -O https://app.syntheticcfo.com/sdk/syntheticcfo.py
```

## An account and a key

A free account needs no card: all three platforms and the core four cycles,
within the free plan's limits on scale and on datasets a month, which
https://app.syntheticcfo.com/evaluate states. Make one at https://app.syntheticcfo.com/?signup=1,
then create an API key under Account, API access. The key carries your plan and
monthly allowance. It cannot manage your account, read your chats or create more
keys.

## One command

```bash
syntheticcfo generate --key scfo_YOURKEY --platform oracle --industry retail --fraud high --rows 600
```

It builds the world, waits, and saves the package zip where you stand.
`python -m syntheticcfo` does the same thing everywhere.

| Command | What it does |
|---|---|
| `syntheticcfo generate` | builds a world, waits, and saves the package zip |
| `syntheticcfo download JOB_ID` | waits for a job if it is still building, then saves its package |
| `syntheticcfo reproduce JOB_ID` | runs a job again with its seed and configuration, then saves the new package |
| `syntheticcfo score JOB_ID FILE` | grades your detector's flagged record ids (a file, or `-` for standard input) against the job's answer key |
| `syntheticcfo me` | shows the account the key belongs to: plan, this month's generations, limits |
| `syntheticcfo jobs` | lists your recent generations, with the engine each ran on and what Reproduce would do |

Every command takes `--key` (or reads `SCFO_API_KEY`); `generate`, `download`
and `reproduce` also take `--lab` and `--out`. `generate` also takes:

| Option | What it sets |
|---|---|
| `--platform` | SAP_ECC (the default), SAP_S4HANA or ORACLE, plain words like oracle work; or a combined environment, SAP_MIGRATION_PAIR or INTERCOMPANY_GROUP, on the Team plan and above |
| `--modules` | the cycles, any of P2P O2C CE R2R HR MM FA (default: the core four, P2P O2C CE R2R) |
| `--industry` | TMT (the default), PHARMA, MANUFACTURING, FINANCIAL_SERVICES or RETAIL |
| `--fraud` | none, low, medium (the default) or high |
| `--rows` | the scale: base transactions per cycle, the setting the plans call rows (not a count of the rows the tables end up holding), up to your plan's limit (`syntheticcfo me` shows it). A value outside the platform's range is first brought into it; a value then above your plan's limit is refused with a message naming the limit. Left out, the world takes the standard volume; when your plan allows less, as the free plan does, it is sized at your plan's limit instead and the command prints the platform's note saying so |
| `--jurisdiction` | a country code such as US, DE, ZA or JP (`syntheticcfo generate --help` lists them) |
| `--seed` | any integer; the same seed and configuration on the same engine version rebuild the world byte for byte (on another engine version they can build a different dataset). Left out, a fresh one is drawn and printed at the end |
| `--years` | 1 for a single year; 2 to 5 for a multi-year arc on the Team plan and above (3 at most on the intercompany group; the migration pair is one year) |
| `--evaluation-mode` | withholds the ground-truth marker columns and moves the annotations out of the shipped data |
| `--lab` | also saves the JSON-lines lab export: one file per table, the answer key and the dataset card |
| `--out` | the folder or file path for the downloads (default: where you stand) |

## From Python

```python
from syntheticcfo import Client

c = Client(api_key="scfo_...")            # or set the SCFO_API_KEY env var
me = c.me()
print(me["plan"], me["generations"])      # your plan and this month's generations

job = c.generate(
    platform="ORACLE",                    # SAP_ECC | SAP_S4HANA | ORACLE
    industry="RETAIL",                    # TMT PHARMA MANUFACTURING FINANCIAL_SERVICES RETAIL
    fraud="high",                         # none | low | medium | high
    rows=600,                             # the scale, up to your plan's limit: me["caps"]["max_rows"]
    modules=["P2P", "O2C"],               # subset of P2P O2C CE R2R HR MM FA
    jurisdiction="ZA",
    seed=42,
    wait=True,                            # poll until the package is built
)

package = job.download("out/")            # the four-layer audit package (zip)
lab = job.lab_export("out/")              # JSONL tables + answer_key.jsonl + dataset card
print(job.insights())                     # what was planted, by control
```

`generate` also takes `volume_tier`, `company_code` (the SAP company code on
every document; the intercompany group keeps its subsidiaries' own codes),
`region`, `size_band`, `fiscal_year`, `years`, `entropy`,
`fx_share` and `blind` (the blind evaluation track, Team plan and above). What
the platform says when it accepts a request, such as the scale it chose when
no row count was given, is on `job.notes` and printed unless `quiet=True`.

For model evaluation, add `evaluation_mode=True`: the shipped database and
workbooks then withhold the ground-truth marker columns, and the annotation
layer moves to `01_Audit_Evidence` beside the answer key, so no column names a
scheme on its own rows. The answer key itself ships in full. A scheme built on
one account (phantom revenue on an invented customer) is still isolated by that
account once a detector finds it.

## Score a detector

```python
flagged = my_detector(lab)                # your detector's record ids, as the tables spell them
result = job.score(flagged)               # graded against this job's answer key
print(result["precision"], result["recall"], result["f1"], result["grade"])
```

A flag is a record id as the answer key spells it: a document number, or a
composite such as `4500000123/10` for a purchase-order line, which also matches
on the purchase order alone and never on the line number alone. On a multi-year
arc, `FY2024:4500000123` grades a flag inside that year; a bare id counts in
every year that labels it. `job.score(text=...)` takes ids separated by
newlines, commas or spaces, or a CSV with a `record_id` column (a `fiscal_year`
column beside it names the year on an arc), as `syntheticcfo score` does. A
file of that one column works too: the client leaves its header line out of
what it sends. The result carries precision, recall, F1, the grade, the
counts and recall per scheme. More on reading a score:
https://app.syntheticcfo.com/evaluate

## Reproduce

```python
print(job.reproduce_note)                 # what Reproduce will do, before you call it
again = job.reproduce(wait=True)          # the same seed and configuration, as a new job
```

On the engine version the original ran on, Reproduce uses no generation and,
for a job that completed, rebuilds the same files. On another engine, or when
the platform has no record of the original's engine, the same seed and
configuration build a new dataset (its certificate names the engine), which
counts as one generation on a metered plan. The platform's note on the new job
says which. A job from before the platform stored configurations cannot be
reproduced; the platform refuses it and says why.

## Notes

- Plan limits apply exactly as in the app. A request for a module, platform or
  feature your plan does not include is refused with a 402 naming what is
  missing. A scale outside the platform's range is first brought into it,
  without a note; a scale then above your plan's limit is refused with a 402
  naming the limit. A request with no row count takes the volume tier's size
  (the standard tier's unless `volume_tier` names another); when that size is
  more than your plan allows, as the standard tier's is on the free plan, it
  is sized at your plan's limit instead and the platform says so. Once the
  month's generations are spent, a request that would use one is refused the
  same way until the allowance resets on the 1st; the `generate` request that
  leaves a single generation for the month carries a note saying so.
- A generation that fails, or that a server restart interrupts, refunds its
  metered credit automatically. A generation you cancel still counts toward the
  month on the free plan.
- The client is MIT licensed (the LICENSE file in the package). Datasets
  generated on the free plan are for evaluation and non-commercial use and may
  not be redistributed, resold, or used to train or evaluate a competing
  product; commercial use needs a paid plan. The free public sample carries its
  own licence: Free for evaluation and non-commercial research, including
  publishing results on it with a citation. Not to be redistributed as files,
  resold, or used to train or evaluate a competing product. Full terms:
  https://app.syntheticcfo.com/terms
