ENGINE 01

SFS-E01-CLS · CLOSE AUTOMATION · REV 2026-07-20 · PRODUCTION

PRODUCTION = runnable end-to-end, CI-backed, full test suite passing. All data fictional and seeded.

Month-End Close

A deterministic month-end close that cannot silently go wrong — ten controls, proven

The engine runs a full month-end close on a fictional three-entity group: it computes nine classes of recurring journal entry (prepaid amortization, depreciation, deferred rent and CAM, fixed-fee and management-fee accruals, note interest, G&A and insurance allocations, and exact-route postage), ties every backing schedule back to the ledger, and writes a JE register, an updated trial balance, and a close report. Before the close counts as clean, ten independent Close Sentinel controls re-check it for the ways a month-end silently goes wrong — a missed accrual, a one-sided intercompany entry, an asset depreciating past its life — and a CRITICAL finding blocks the close and fails the run's exit code. All money is integer cents so tie-outs are exact, and every run is seeded and reproducible.

A controller cannot take "the close is clean" on faith. Every figure on this page re-derives from the public repository by running the commands shown — detection is proven, not asserted.

Run it Book a consultation

Architecture

functional block stack · static overview
CLI & exit-code gate Close Sentinel — ten independent controls Recurring-entry engine & tie-out Accounting model & balance control Integer-cent money primitives Seeded synthetic data generator substrate: seeded fictional data

Select a layer

Each layer is an independent stage of the close, stacked from the pipeline gate down to the seeded data. Select a layer for its plain-terms and engineering detail.

Key specifications

at a glance
9 classes1
Recurring entries automated
Nine kinds of month-end journal entry, each with a backing schedule
10 (C1-C10)2
Deterministic controls
Ten independent checks; a single CRITICAL blocks the close
12 faults, all caught3
Fault-injection coverage
Twelve classic close errors, each caught by its mapped control
15,6874
Tests
Tests pytest collects for this engine
integer cents5
Money precision
All amounts are whole cents; allocations use largest-remainder
3 entities (DH/MF/BW)6
Demo entity group
A fictional three-entity group generated from a fixed seed

What it does for you

plain terms

Month-end close at a large entity group is a recurring operational burden: many interdependent recurring journal entries, each with its own logic, spread across multiple operating entities and separate accounting databases. Run from memory or a static spreadsheet, errors accumulate and the close extends for days. The failures that hurt most are the silent ones — a recurring accrual that quietly stops posting, a fully depreciated asset that keeps depreciating, an allocation driven by a cumulative balance instead of the month's activity. Each looks exactly like a clean close until someone finds it quarters later.

A clean March close7. Running the close for 2026-03 posts all nine recurring entries balanced, ties every schedule to the GL (prepaid 8,100.00, fixed-fee 62,250.00, insurance 10,950.00, and postage account 1460 clearing to 0.00 in each entity), lands the trial balance at Dr 3,603,311.50 = Cr 3,603,311.50, and Sentinel reports zero findings — close status CLEAN, exit 0.

Prove the controls with injected faults8. The guardrail demo first runs a clean baseline (zero findings), then injects twelve classic close errors one at a time — an unbalanced opening, an ended asset still depreciating, a one-sided intercompany entry, a missing accrual, a rounding drift, a single-cent shadow tamper, a rewritten locked period, and more — and asserts each is caught by exactly the control designed for it. Verdict: ALL GUARDRAILS HELD (12 faults, baseline clean), exit 0.

Autonomous remediation of a drifted close9. Given a close contaminated with a drift profile (19 initial findings), the Autonomous Close Loop resyncs four drifted categories from the seeded sub-ledger of record over four turns (criticals fall 18 -> 1), quarantines the tampered locked prior period it has no authority to overwrite, and posts the current period on its own authority — verdict AUTO-POSTED (PARTIAL), exit 0, with a byte-identical settled register.

Functional block diagram

engineering · each block links to its source
Functional block diagram of the Month-End Close engine seeded sub-ledgers exact cents post (balance gate) schedules tied close re-derive from source raw data C9: must agree to the cent register + TB control findings artifacts CRITICAL blocks -> exit 1 findings verdict -> exit PASS / NEEDS REVIEW Seeded dataset Integer-cent math Recurring-entry engine Ledger +balance control Read-onlypreflight family Schedule to GL tie-out Shadow recompute (C9) Close Sentinel C1-C10 Register / TB / report Autonomous close loop Verdict +exit-code gate SFS-E01-CLS · FUNCTIONAL BLOCK DIAGRAM · REV 2026-07-20

Plain terms

  • Seeded dataset. Fictional 3-entity group + sub-ledgers
  • Integer-cent math. Exact splits, no penny lost
  • Recurring-entry engine. Computes & posts 9 recurring JEs
  • Ledger + balance control. Debits must equal credits, per entity too
  • Read-only preflight family. 13 validation-only checkers (PASS / NEEDS REVIEW)
  • Schedule to GL tie-out. Each schedule must reconcile to its account
  • Shadow recompute (C9). A second, independent computation of every amount
  • Close Sentinel C1-C10. Ten controls block a silent close error
  • Register / TB / report. Writes JE register, trial balance, close report
  • Autonomous close loop. Auto-remediates in-period drift, quarantines what it cannot certify
  • Verdict + exit-code gate. Clean close => exit 0; otherwise non-zero

Engineering

  • Seeded dataset. generate_dataset(period, seed=2026): entities, opening TB, prepaids, assets, leases, notes, insurance, postage.
  • Integer-cent math. split_evenly, allocate_by_ratio (largest-remainder; weights sum to 10000 bps).
  • Recurring-entry engine. CloseEngine.run(): refuse invalid splits/postage, post 9 categories, balance gate per entity.
  • Ledger + balance control. Ledger.post() raises OutOfTieError on aggregate or per-entity imbalance.
  • Read-only preflight family. recurring_register, tb_package, group_operations, project_job_cost, personal_property_tax, payroll_recovery, headcount, je_package, budget_variance, cash_draw, trial_balance, audit_pbc, consolidation — never post.
  • Schedule to GL tie-out. _tie_out() -> TieResult per schedule; prepaid ties to acct 1400, fixed-fee to 2350, insurance to 1450, postage to 1460.
  • Shadow recompute (C9). sentinel/shadow.py re-derives (entity, category, account) -> (dr, cr) importing nothing from the engine; the posted register must match to the cent.
  • Close Sentinel C1-C10. run_sentinel(dataset, result) -> SentinelReport; any CRITICAL => not clean.
  • Register / TB / report. write_outputs(): je_register.md/.json, trial_balance.md/.json, schedules.json, close_report.md, sentinel.json.
  • Autonomous close loop. autonomous_close_loop(): observe -> detect -> remediate -> re-verify -> gate; resync the earliest drifted category from the seeded source of record; verdict AUTO-POSTED / AUTO-POSTED (PARTIAL) / HALTED. Deterministic and offline — no model calls.
  • Verdict + exit-code gate. cli.main() and loop.verdict_exit_code(); a CRITICAL finding, a refused entry, or a failed tie => exit 1.

Instruction set

every public command
CommandOperationOutputExitArtifacts
python -m close_engine.generate --period 2026-03 --seed 2026
Inspect the seeded synthetic datasetPrints the fictional entity group and sub-ledgers0none (stdout only)
python -m close_engine --period 2026-03 --out ./output
Run a full month-end close (Sentinel on by default)Tie-out summary + 'Close status: CLEAN'0 clean; 1 if refused / failed-tie / unbalanced / CRITICALoutput/je_register.md+.json, trial_balance.md+.json, schedules.json, close_report.md, sentinel.json
python run.py --period 2026-03 --out ./output
Same close via the convenience wrapperIdentical to python -m close_engine0 / 1 (same semantics)same as above
python -m close_engine --demo-guardrails
Inject 12 faults; prove each is caught by its controlPASS table + 'Demo verdict: ALL GUARDRAILS HELD (12 faults, baseline clean)'0 only if baseline clean AND all 12 caughtnone (stdout only)
python -m close_engine.loop --demo --out output
Drift a close + a locked prior; run the autonomous loop'Verdict: AUTO-POSTED (PARTIAL)' + turn-by-turn log0 (AUTO-POSTED / PARTIAL); 1 (HALTED)output/autonomous_close_loop.md + .html
python -m close_engine.recurring_register --input samples/sample-recurring-register.json
Read-only preflight of a recurring-JE registerPreflight summary + 'Verdict: PASS'0 PASS; non-zero on NEEDS REVIEWnone; validation only (0 posting actions, 0 import payloads)
python -m pytest -q
Run the test suitepytest pass/fail summary0 if all passnone

Benchmarks

measured demo results
MeasureResult
Clean close, trial balance103,603,311.50 USD (Dr = Cr), 9 posted / 0 refused, exit 0
The March close balances to the cent with nothing refused
Guardrail fault coverage1112 / 12 faults caught, baseline 0 findings, exit 0
Every injected close error caught by its mapped control
Autonomous loop remediation1218 -> 1 criticals over 4 turns; verdict AUTO-POSTED (PARTIAL), 1 quarantined, exit 0
The loop clears in-period drift down to a single quarantined item and still posts
Postage exact-route allocation134 / 5 meter rows routed; acct 1460 clears to 0.00 in every entity
Four of five postage rows route to an approved job; the clearing account nets to zero

Control characteristics

engineering

Plain terms

The posting engine runs against seeded synthetic data and never touches a real ERP or source of record. Every read-only preflight is explicit that a mechanically clean result is only 'READY FOR HUMAN REVIEW' — source refresh and tie, mapping, and controller sign-off remain manual human gates. The autonomous loop replaces the person only for deterministic, logged in-period remediation, and still HALTS on an opening imbalance (C1) and quarantines a locked-period tamper (C10) rather than acting unilaterally.

Engineering

Deterministic envelope. seeded fictional inputs, offline default mode.

Demo gate. deterministic-policy

AuthorityLevelNote
1HALT (C1 opening imbalance)Action-authority precedence in the autonomous loop, not an evidence hierarchy. The loop has no authority to invent a broken opening carryforward, so it refuses to post and escalates to a human. HALT_CONTROLS = {C1} in loop.py; exit 1.
2QUARANTINE (C10 locked-period tamper)A signed-off, locked prior period was altered; the loop will not overwrite it — it holds and logs, and still posts the current period. QUARANTINE_CONTROLS = {C10} in loop.py; verdict AUTO-POSTED (PARTIAL).
3AUTO-REMEDIATE (C2-C9 in-period drift)The loop is authorized to resync a drifted recurring-entry category to the seeded sub-ledger of record. AUTO_REMEDIABLE = {C2,C3,C4,C5,C6,C7,C8,C9} in loop.py.
SeverityVerdictAction
Sentinel: any CRITICAL (C1-C10)NOT CLEANClose blocked; CLI exit 1
Sentinel: WARN (e.g. C7 step-change)clean, reviewer-escalatedClose proceeds; flagged for a reviewer
Sentinel: INFOcleanCorroborated note kept in the audit trail
Loop: auto-remediable CRITICAL (C2-C9)AUTO-POSTEDResync the category from the source of record; exit 0
Loop: C10 locked-period tamperAUTO-POSTED (PARTIAL)Quarantine + log; post the current period; exit 0
Loop: C1 opening imbalanceHALTEDEscalate to a human; exit 1
Preflight: gate failsNEEDS REVIEWRead-only; never posts (0 posting actions / 0 import payloads)

Authoritative close (posting). CloseEngine.run() computes and posts the nine recurring JEs, writes the register / TB / report, and Sentinel gates the exit code (cli.py).

Read-only preflight (validation). 13 validation-only modules validate an external JSON package and return PASS / NEEDS REVIEW; they never create a JE, import payload, or posting action (recurring_register.py and siblings).

Guardrail demo (proof). --demo-guardrails injects 12 seeded faults and asserts each is caught by its mapped control at a qualifying severity (faults.py).

Autonomous close loop (remediation). loop.py drives a drifted close back to a certifiable state (observe -> remediate -> re-verify -> gate) with a deterministic, logged gate policy. It is always offline and deterministic, with no model calls.

Operating limits

what it refuses to do

See it run

animated overview
Animated overview tile for the Month-End Close engine — the close pipeline and its ten Close Sentinel controls rendered as brand motion on fictional seeded data.
Animated overview of the close run on fictional seeded data; run the commands above to reproduce.

Integration

how to run it

Distribution: public repository, MIT license.

git clone https://github.com/sophonfinance-wq/finance-automation-portfolio.git && cd finance-automation-portfolio/monthly-close-automation && python -m close_engine --period 2026-03 --out ./output
python -m close_engine --demo-guardrails
Source on GitHub Engine README All tests, by engine Run in Codespaces
Substantiation
  • 1monthly-close-automation/README.md 'What the engine computes' table; close_engine/engine.py run() posts 9 categories
  • 2close_engine/sentinel/controls.py ALL_CONTROLS list (C1..C10)
  • 3close_engine/faults.py FAULTS dict; verified via `python -m close_engine --demo-guardrails` -> 'ALL GUARDRAILS HELD' (exit 0)
  • 4python -m pytest --collect-only -q (monthly-close-automation/); verified 2026-07-20 and pinned in site-datasheets/counts.json
  • 5close_engine/money.py (to_cents, split_evenly, allocate_by_ratio)
  • 6close_engine/generate.py generate_dataset(); output/close_report.md 'Entity group'
  • 7monthly-close-automation/README.md 'Real example output' console summary + output/close_report.md; re-run: python -m close_engine --period 2026-03 --out ./output
  • 8monthly-close-automation/README.md 'Fault-injection demo'; re-run: python -m close_engine --demo-guardrails
  • 9monthly-close-automation/README.md 'Autonomous Close Loop' + output/autonomous_close_loop.md; re-run: python -m close_engine.loop --demo --out output
  • 10Ran python -m close_engine --period 2026-03 (seed 2026); matches output/trial_balance.md and README console summary (Dr 3,603,311.50 / Cr 3,603,311.50 [OK])
  • 11Ran python -m close_engine --demo-guardrails -> 'ALL GUARDRAILS HELD (12 faults, baseline clean)'
  • 12output/autonomous_close_loop.md (19 initial findings); re-run: python -m close_engine.loop --demo --out output
  • 13output/close_report.md tie-out; README console summary 'Postage routes : 4/5 meter rows'
  • 14monthly-close-automation/README.md 'Scope and assumptions'
  • 15monthly-close-automation/README.md validation-only sections; close_engine/recurring_register.py and siblings
  • 16close_engine/loop.py (HALT_CONTROLS, QUARANTINE_CONTROLS); README 'Autonomous does not mean ungated'
  • 17monthly-close-automation/README.md 'Scope and assumptions'

Show us where the hours go.

One conversation: you describe the work that consumes your team's month; we tell you plainly what this engine can take over, what it can't, and what a scoped first phase would cost. Your people keep approval authority.

Book a free consultation