Early accessSome features may be unavailable
Back to Blog
tutorialmulti-periodteamauditdatasynth

Multi-Period Continuity: Generating Coherent Full-Year Audit Data

Multi-period chains in DataSynth 5.3/5.5 thread closing trial balances into the next period's opening balances, run FX revaluation at each period boundary, and roll retained earnings correctly. This walkthrough covers the schema, a worked 4-quarter manufacturing example, the on-disk output structure, how to verify carryover, and the pricing model (N × per-period, no premium).

VynFi Team · EngineeringMay 8, 202610 min read

Three days ago we shipped DataSynth 5.5 and exposed the multi-period chain feature to Team+ customers. This post is the longer, hands-on version: what the chain actually is, when you should use it, the full schema, a worked 4-quarter example, and how to verify the carryover is correct.

**TL;DR** — Set `multiPeriod.enabled = true` and provide `periodSpecs[]` with 2–12 strictly-increasing fiscal periods. The chain runs as one job; each period's closing trial balance becomes the next period's opening balance. FX revaluation runs at boundaries; retained earnings rolls correctly; document chains span boundaries. Output structure: one folder per period (`period-q1/`, `period-q2/`, ...) plus a chain-level manifest. Pricing: N × per-period cost, no chain premium.

The problem: standalone single-period jobs lose state

Generating a single fiscal period of audit data is a solved problem. You pick a sector, set the row count, choose distributions, and the engine emits a coherent set of journal entries, master data, and document flows. Trial balances reconcile. Document chains close. Auditing the output is straightforward.

Generating a *full year* of audit data — coherent across four quarters — is harder. The naïve approach is to run four single-period jobs and glue the outputs together. This breaks immediately. The Q1 job knows nothing about Q2; opening balances are arbitrary, retained earnings doesn't roll, FX positions aren't revalued at period boundaries, document chains opened in Q1 don't close in Q3. You can patch some of this manually — extract closing TB from Q1, reformat as opening TB for Q2 — but you'll spend more time on file plumbing than on actual audit testing, and you'll get the FX boundary wrong.

This was the recurring complaint from audit firms running our platform: 'multi-period sessions exist, but they don't actually thread the state.'

The solution: `multiPeriod.enabled = true` with `periodSpecs[]`

DataSynth 5.3 added a chain-aware multi-period generator. DataSynth 5.5 is when the API surface stabilized. Set `multiPeriod.enabled = true` on a generation request, provide a `periodSpecs[]` array with 2–12 strictly-increasing fiscal periods, and the engine runs the chain as one logical job. Closing TB at the end of each period becomes the opening TB at the start of the next. FX revaluation runs at each period boundary. Retained earnings rolls. Document chains opened in one period can close in a later period.

Schema

JSON
{
"preset": "manufacturing_mid",
"rows": { "journal_entries": 50000 },
"multiPeriod": {
"enabled": true,
"periodSpecs": [
{
"fiscalPeriod": "2025-Q1",
"periodMonths": 3
},
{
"fiscalPeriod": "2025-Q2",
"periodMonths": 3
},
{
"fiscalPeriod": "2025-Q3",
"periodMonths": 3
},
{
"fiscalPeriod": "2025-Q4",
"periodMonths": 3
}
]
}
}
  • `enabled` — boolean. When true, requires Team plan or above; otherwise the API returns a `tier_restricted` validation error.
  • `periodSpecs[]` — 2 to 12 entries. Strictly increasing by fiscalPeriod. Period count outside [2, 12] returns a validation error.
  • `fiscalPeriod` — your fiscal-period label (e.g., `2025-Q1`, `2025-04`, `FY2025-H1`). Format-flexible but consistent across the chain.
  • `periodMonths` — months covered by the period. Must be consistent across the chain (a 3-month period followed by a 6-month period is rejected).

Worked example: 4-quarter manufacturing chain

Walking through the example end-to-end. We'll generate a year of mid-size manufacturing data — 50,000 journal entries per quarter — with closing-TB carryover, FX revaluation against EUR, and the manufacturing sector pack.

Bash
curl -X POST https://api.vynfi.com/v1/generate \
-H "Authorization: Bearer vf_live_..." \
-H "Content-Type: application/json" \
-d @config.json

Where `config.json` is:

JSON
{
"preset": "manufacturing_mid",
"rows": { "journal_entries": 50000 },
"fxBaseCurrency": "EUR",
"multiPeriod": {
"enabled": true,
"periodSpecs": [
{ "fiscalPeriod": "2025-Q1", "periodMonths": 3 },
{ "fiscalPeriod": "2025-Q2", "periodMonths": 3 },
{ "fiscalPeriod": "2025-Q3", "periodMonths": 3 },
{ "fiscalPeriod": "2025-Q4", "periodMonths": 3 }
]
}
}

Response — a single job ID for the whole chain:

JSON
{
"id": "job_chain_xyz789",
"object": "job",
"status": "queued",
"credits_reserved": 300000,
"estimated_duration_seconds": 240,
"links": {
"self": "/v1/jobs/job_chain_xyz789",
"stream": "/v1/jobs/job_chain_xyz789/stream"
}
}

Output structure

The chain output ships as one downloadable archive with a flat per-period folder structure plus chain-level metadata:

text
job_chain_xyz789/
├── manifest.json # chain-level: periods covered, counts, hashes
├── period-q1/
│ ├── journal_entries.parquet # Q1 transactions
│ ├── trial_balance_opening.csv # Q1 opening TB (zero, fresh entity)
│ ├── trial_balance_closing.csv # Q1 closing TB (becomes Q2's opening)
│ ├── chart_of_accounts.csv
│ └── master_data/
├── period-q2/
│ ├── journal_entries.parquet # Q2 transactions
│ ├── trial_balance_opening.csv # = Q1 trial_balance_closing.csv
│ ├── trial_balance_closing.csv # becomes Q3's opening
│ ├── chart_of_accounts.csv
│ └── master_data/
├── period-q3/
│ └── ...
└── period-q4/
└── ...

Master data (vendors, customers, materials, chart of accounts) is consistent across periods — same vendor IDs, same customer IDs, same account numbers. New master records added during the year appear in their introduction-period folder and forward.

Verifying carryover

The most common audit-firm question on multi-period output is: 'how do I know the carryover actually worked?' Two checks:

Check 1 — Opening TB equals previous period's closing TB

Python
import pandas as pd
q1_close = pd.read_csv("period-q1/trial_balance_closing.csv")
q2_open = pd.read_csv("period-q2/trial_balance_opening.csv")
# Sort both by account_number; they should be byte-identical except for
# the period label and posting_date metadata columns.
q1_close = q1_close.sort_values("account_number").reset_index(drop=True)
q2_open = q2_open.sort_values("account_number").reset_index(drop=True)
assert (q1_close["balance"] == q2_open["balance"]).all()
print("Q1 closing TB == Q2 opening TB ✓")

Check 2 — Retained earnings rolls correctly

At each period close, the engine posts a retained-earnings roll: P&L net for the period (revenue + expense closing entries) → retained earnings. Verify by walking the retained-earnings account across all four trial balances:

Python
tbs = [pd.read_csv(f"period-{q}/trial_balance_closing.csv") for q in ["q1", "q2", "q3", "q4"]]
re_balances = [tb[tb["account_number"] == "3300"]["balance"].iloc[0] for tb in tbs]
print(f"RE: Q1={re_balances[0]:,} Q2={re_balances[1]:,} Q3={re_balances[2]:,} Q4={re_balances[3]:,}")
# RE should grow monotonically (assuming profitable year);
# delta = period net income from the income statement.

Pricing — pay-per-period, no chain premium

We want to flag this clearly because it's a deliberate design choice. A 4-quarter chain costs exactly 4× what a single-period job costs. The chain saves you operational time (no manual closing-TB → opening-TB plumbing, no FX-boundary wrangling) but does not save you credits.

text
Per-period cost:
50,000 rows × 1 credit/row × 1.5x sector (manufacturing) = 75,000 credits
Chain cost:
4 × 75,000 = 300,000 credits
Equivalent in dollars on Team plan ($199/mo, 5M credits included):
300,000 credits = 6% of monthly allowance, no overage.

If you don't need temporal coherence — say you're testing four independent month-end reconciliations — run four standalone jobs and pay the same total. The pricing model deliberately doesn't lock you into the chain.

Tier gate

`multiPeriod.enabled = true` requires the Team plan ($199/mo) or above. Free and Developer tiers receive a 422 `tier_restricted` validation error pointing at the upgrade path:

JSON
{
"type": "https://api.vynfi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "1 validation error in request body",
"fields": [
{
"field": "multiPeriod.enabled",
"message": "Multi-period chain requires the Team plan or above. Upgrade at dashboard.vynfi.com/billing.",
"code": "tier_restricted"
}
]
}

What's next

Wave 1 of the DS 5.5 adoption ships standalone multi-period chains — single-entity, fixed periodMonths across the chain. Wave 2 (Group Audit MVP) will extend the chain to multi-entity scenarios where each entity has its own chain and the chains roll up into a consolidated group view at year-end. Wave 3 will add hyperinflation (IAS 29), step-acquisitions (IFRS 3), and CGU goodwill testing (IAS 36) — the full year-end consolidation toolbox.

For now, on Team+ you have a clean four-quarter chain. Try it, run the verification scripts, and let us know what breaks at support@vynfi.com.

Ready to try VynFi?

Start generating synthetic financial data with 10,000 free credits. No credit card required.