Back to Blog
tutorialmulti-periodauditdatasynth

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 — credits are the only meter).

VynFi Team · EngineeringMay 8, 202610 min read

Three days ago we shipped DataSynth 5.5 and exposed the multi-period chain feature to every account — credits are the only meter. 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. Available on every account; credits are the only meter. A chain that costs more credits than your balance returns HTTP 402.
  • `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 ÷ 3 × 1.5x sector (manufacturing) = 25,000 credits
Chain cost:
4 × 25,000 = 100,000 credits
Credits are the only meterno subscription, no monthly allowance.
Top up anytime with one-time credit packs when your balance runs low.

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.

Credits, not tiers

`multiPeriod.enabled = true` is available on every account — there are no tiers and no feature gates; credits are the only meter. The one balance-related error is HTTP 402, returned when a chain's credit cost exceeds your available balance:

JSON
{
"type": "https://api.vynfi.com/errors/insufficient-credits",
"title": "Insufficient Credits",
"status": 402,
"detail": "This chain costs 100,000 credits but your balance is 40,000.",
"credits_required": 100000,
"credits_available": 40000
}

Top up with a one-time credit pack from the billing page and resubmit — no subscription required.

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 any account 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 5,000 free credits. No credit card required.