ENGINE 36
SFS-E36-DEP · ASSET REGISTER · REV 2026-07-24 · PRODUCTION
PRODUCTION = runnable end-to-end, CI-backed, full test suite passing. All data fictional and seeded.
Depreciation & Prepaid Amortization
Does every derived figure re-derive from cost, life and window, and do the totals tie?
An entity closes a month with a fixed-asset register and a prepaid schedule full of tiny derived numbers -- a monthly charge per asset, a months-used count, an accumulated balance, a net book value, a per-item amortization waterfall -- and every one of them was once a formula somebody could have overtyped. This engine proves, row by row, that each stated figure was recomputed from the base facts rather than typed, and that the register totals tie the accounts and the entry that post them. The monthly charge is cost over life in integer cents; accumulated depreciation is that base times months-used, capped at cost; net book value is cost less accumulated; every prepaid figure is the additions spread across the stated amortization window. This engine reads the eight artifacts a month-end fixed-asset and prepaid close emits -- the fixed-asset register, the useful-life class table, the capitalization policy and its expense log, the prepaid register, the disposal register, the accumulated-depreciation and prepaid roll-forwards, the GL control balances and the posted monthly recurring journal entry -- and runs thirty deterministic controls over them. Is the close file complete and the close month readable, does every expense item sit on the correct side of the capitalization threshold and every capitalized asset meet it, is every asset's class in the life table and its life the class's (or its lease term) and everything straight-line, does the monthly charge re-derive as cost over life and the close-month expense match the schedule, is the end-of-depreciation month in-service plus life with everything past it fully accumulated at zero net book value and nothing depreciating before service, is months-used capped at the life and the accumulated balance and net book value re-derived, do the accumulated and prepaid roll-forwards close on themselves and foot the registers they summarise, does every prepaid waterfall figure re-derive from its window with the first booked month taking the catch-up, is every disposed asset off the register and each write-off exactly the net book value removed, and do the cost, accumulated-depreciation and prepaid control accounts and the posted recurring journal entry each tie the register footings. It compares every figure with exact ==, gates the threshold with exact >=, measures every date to the close month the file carries rather than a clock, and never writes to a source artifact.
A depreciation register is the safest-looking schedule in the ledger: a column of small charges, each a fraction of a cost, footing to a total that has tied for years. That is exactly why it drifts. None of the ways it goes wrong look wrong in a register that still foots. A useful life is keyed off the class table, a monthly charge is overtyped, an asset keeps depreciating a month past its end date or starts a month before it enters service, and every downstream month inherits the error while the column still adds up. Accumulated depreciation is a running balance -- opening plus the month's charge less what a disposal removed -- and a balance maintained by hand drifts the first time a row is edited and nobody re-adds the column; prepaid balances break the same way. And the register total quietly stops tying: the register says one number, the GL control account says another, and the recurring entry posts a third, each plausible alone. The engine rebuilds every derived figure from cost, life, convention and the stated amortization window through one schedule kernel and compares with exact ==, closes both roll-forwards and foots them to their registers, ties the register totals to the GL control accounts and the posted recurring entry, ships a planted-defect file for every registered control, and states its benchmark as a command rather than a claim. A life keyed wrong, a balance re-typed, or a total that stopped tying is invisible until someone re-derives the register the books already posted.
Architecture
functional block stack · static overviewSelect a layer
Eleven control families run in registry order over each close file. The first proves the others had something to read and that the close month is legible, because a control that passes on absent evidence reports assurance it never performed.
Key specifications
at a glanceWhat it does for you
plain termsAn entity closes a month with a fixed-asset register and a prepaid schedule full of tiny derived numbers -- a monthly charge per asset, a months-used count, an accumulated balance, a net book value, a per-item amortization waterfall -- and every one of them was once a formula somebody could have overtyped. Three failures hide inside that, and none of them look wrong in a register that still foots. The derivation drifts: a useful life is keyed off the class table, a monthly charge is overtyped, an asset keeps depreciating past its end date or starts before it enters service, and every downstream month inherits the error while the column still adds up. The roll-forward breaks: accumulated depreciation is a running balance -- opening plus the month's charge less what a disposal removed equals closing -- and a balance maintained by hand drifts the first time a row is edited and nobody re-adds the column, and prepaid balances break the same way. The totals stop tying: the register says one number, the GL control account says another, and the recurring entry posts a third, each plausible alone. None of these are judgment calls. They are equalities -- a monthly charge against cost over life with exact ==, a life against the class table, an accumulated balance against its re-derivation, a control-account balance against the register footing -- which a deterministic control settles better than a spreadsheet inherited by whoever maintains the register this year.
The seeded demo runs every control over every close file7. The CLI generates thirty-two fictional close files -- one clean baseline and one carrying each of the thirty-one planted defects -- then runs all thirty controls over each. The clean file returns PASS with no flags; every defect file returns REVIEW or FAIL and names the control it tripped along with the reason that control exists.
The lifecycle gates are genuinely independent8. The date and net-book-value controls partition the assets by stage, so a break at one boundary trips exactly one control. One defect file leaves net book value on an asset that finished its schedule and trips date_fully_depreciated_zero; another books accumulated depreciation on an asset not yet in service and trips date_not_in_service_yet -- each row re-derived so the cost-less-accumulated identity still holds, and the in-life accumulated control skips both, proving the gates own different failures.
The roll-forward is recomputed, not read back9. A defect file shifts the accumulated roll-forward's opening and closing together, so it still closes on itself while drifting off the register it summarises. roll_closing_ties_register rebuilds the footing from the asset register's stated columns and fails, proving the roll-forward is recomputed from the register beneath it rather than trusted because its own identity holds.
Functional block diagram
engineering · each block links to its sourcePlain terms
- Seeded Close Files. fictional month-end close files enter the control registry
- Structural Precondition. is the close file complete and the close month legible
- Capitalization Gate. is every item on the correct side of the threshold
- Useful-Life Class Mapping. is every life and method the class table's
- Straight-Line Re-derivation. does every charge re-derive from cost and life
- In-Service & End-Date Gates. do the service and end-date gates hold
- Net Book Value. does the accumulated balance and NBV re-derive
- Balance Roll-Forwards. do the roll-forwards close and foot the registers
- Prepaid Amortization Waterfall. does every prepaid waterfall figure re-derive
- Disposal Roll-Off. are disposals rolled off, written off, and watched
- GL Control-Account Tie-Out. do the control accounts foot the registers
- Recurring-JE Tie-Out. does the posted entry equal the register totals
- Registers, Table & Kernel. the artifacts and shared kernel every rule resolves against
- Verdict and Findings. every finding, with the reason it exists, ends at a person
Engineering
- Seeded Close Files. generate_corpus writes one clean baseline plus one planted-defect file for every registered control, thirty-two files in total. Only the base facts are stated -- each asset's cost, class, method, in-service month and life, each prepaid item's additions and amortization window, the policy threshold and the disposal facts -- and every derived column the engine later re-checks (the monthly charge, months used, accumulated depreciation, the close-month expense, net book value, the end-of-depreciation month, the prepaid waterfall figures, both roll-forwards, the GL control balances and the recurring entry) is computed through the same schedule kernel the engine later recomputes with, so the relationships the engine tests are the same relationships that produced the data.
- Structural Precondition. One control. FAILs a file missing any of the eight artifact types or carrying a duplicate, or an unreadable close month, so no downstream rule holds vacuously on absent evidence.
- Capitalization Gate. Two controls: every expense-log item sits on the correct side of the capitalization threshold in both directions, and no capitalized asset on the register falls below it, gated with exact >=.
- Useful-Life Class Mapping. Three controls: every asset's class is in the life table, its life is the class's fixed life or its own lease term, and every asset and class row states the straight-line method.
- Straight-Line Re-derivation. Two controls: the monthly charge is cost over life in integer cents, and the close-month expense is the schedule's charge, with the final month absorbing the remainder so the schedule sums to cost exactly.
- In-Service & End-Date Gates. Three controls that partition assets by lifecycle stage: the end month is in-service plus life, an asset past it is fully accumulated at zero net book value, and an asset before service carries no depreciation.
- Net Book Value. Three controls: months-used is elapsed capped at the life, the in-life accumulated balance re-derives as base times months-used capped at cost, and net book value equals cost less accumulated inside [0, cost].
- Balance Roll-Forwards. Three controls: the accumulated-depreciation roll-forward closes on itself, its closing and charge foot the asset register, and the prepaid roll-forward closes and foots the prepaid register.
- Prepaid Amortization Waterfall. Five controls: the window is usable, the monthly charge and the close-month expense (catch-up included) and both balances re-derive from the window, and a window about to end is flagged.
- Disposal Roll-Off. Three controls: no disposed asset is still on the register, each write-off is exactly the net book value removed, and an asset stale past its end month is flagged for disposal review.
- GL Control-Account Tie-Out. Three controls: the cost, accumulated-depreciation and prepaid control accounts each equal the register footing they summarise, compared with exact ==.
- Recurring-JE Tie-Out. Two controls: the posted depreciation line equals the register's total monthly charge, and the posted amortization line equals the prepaid register's monthly total, each posted from the schedule.
- Registers, Table & Kernel. The fixed-asset register carries each asset's cost, class, method, in-service month, life and its derived charge, months-used, accumulated, end month and net book value; the useful-life table carries the life and method per class; the capitalization policy carries the threshold, the excluded categories and the expense log; the prepaid register carries the amortization waterfall; the disposal register carries the write-offs; the roll-forward schedule carries both balance identities; and the GL control balances and the recurring entry carry the amounts the registers must tie to. Every derivation, date, roll-up and tie-out control resolves against these through the shared schedule kernel, so a stated figure cannot disagree with the arithmetic that produced it.
- Verdict and Findings. Findings roll up per close file: any FAIL is FAIL, FLAGs without FAILs are REVIEW, clean is PASS. The CLI exit code is the verdict, so a pipeline can gate on it. Reports carry no timestamps or absolute paths, which is what makes the committed report diffable.
Instruction set
every public command| Command | Operation | Output | Exit | Artifacts |
|---|---|---|---|---|
python run.py | regenerate the seeded fictional corpus, run all thirty controls, write both reports | per-file verdicts and every actionable finding, then the overall verdict | 2 for the bundled corpus, which carries a planted defect for every control by design | dep_report.json, dep_report.md, samples/ |
python -m depreciation_engine samples | analyze an existing folder of close files without regenerating it | per-file verdicts and findings | 0 PASS / 1 REVIEW / 2 FAIL / 3 usage | none unless --json or --md is passed |
python -m depreciation_engine samples --generate --quiet | regenerate the corpus and print only the overall verdict | a single verdict line | 0 PASS / 1 REVIEW / 2 FAIL / 3 usage | samples/ |
python -m pytest depreciation_engine/tests -q | run the full test suite for this engine | 10440 passed | 0 when every test passes | none |
Benchmarks
measured demo results| Measure | Result |
|---|---|
| Engine tests10 | 10,440 tests collected live collection from the engine directory |
| Registered controls11 | 30 controls counted from the registry, not from documentation |
| Planted defects12 | 31 defect files every registered control has at least one file that trips it |
| Control coverage13 | 100 percent of controls with a planted defect no control ships without a file demonstrating it firing |
Control characteristics
engineeringPlain terms
The engine has no write path and therefore no autonomy to gate. It produces findings; a person decides whether a figure is carried into the close and whether an asset is chased for disposal or a prepaid term for renewal.
Engineering
Deterministic envelope. seeded fictional inputs, read-only operation, offline default mode.
Demo gate. FAIL on the bundled corpus, by design -- it carries a planted defect for every registered control
| Severity | Verdict | Action |
|---|---|---|
| No findings above PASS | PASS | carry the mechanically clean close file into the documented review and sign-off |
| One or more FLAG, no FAIL | REVIEW | a human resolves each flag; an asset sitting fully depreciated past the disposal-review band and a prepaid window about to end both land here |
| One or more FAIL | FAIL | the figure is not carried until the failing control is cleared -- a charge that does not re-derive, a life off the class table, a roll-forward that does not close, or a total that does not tie the GL or the recurring entry |
- Read-only: close files are parsed and never written back, so the engine cannot introduce the break it reports.
- Integer cents throughout, compared with exact ==, and the threshold gated with exact >=; there is no tolerance band, and a cent off the re-derivation is reported as a break.
- A figure that should be integer cents but is not produces an AMOUNT_INVALID finding contained to the one row it was read on, rather than being coerced into the number the engine is meant to audit.
- One schedule kernel: the controls and the generator share depreciation_engine.schedule, so a stated figure cannot disagree with the arithmetic that produced it.
- The final month of every schedule absorbs the rounding remainder, so the monthly charges sum back to cost exactly and no penny drifts.
- The first booked month takes the catch-up for a prepaid set up after its window opened, and the clean baseline itself carries one so the catch-up path is exercised by data that passes.
- The date and net-book-value gates partition the assets by lifecycle stage, so exactly one control owns each row and a single break trips a single control.
- Every derivation and date gate is measured to the close month the file carries, and both watch bands come from the file's own figures, never the system clock.
- Deterministic and byte-stable: same inputs produce the same findings in the same order, with no timestamps or absolute paths in any output.
- Absent evidence is never a passing control; a missing artifact fails completeness rather than letting the controls that read it pass unread.
Operating limits
what it refuses to do- The engine never posts a journal entry, closes a period, disposes an asset, renews a prepaid term, or writes to a source artifact. It reads the close file and stops.14
- It reads the eight artifacts the close emits. It does not connect to a general ledger system, a fixed-asset subledger application, an invoice feed or the underlying contracts and leases.15
- It owns the fixed-asset and prepaid subledgers, not their neighbours: the JE-posting cadence of the close, debt and loan amortization, and the prepaid-insurance entity allocation each belong to a separate engine.16
- It re-derives at the cost, life, class life and amortization window the file carries; it does not decide whether that cost is complete, whether the life or capitalization policy is the correct one, or whether the asset physically exists.17
- It takes the disposal and capitalization records at face value: it confirms the register foots, re-derives and ties, not that a disposal or a purchase actually occurred. All shipped data is fictional and the close month is set in a fictional future.18
See it run
control architecture
Integration
how to run itDistribution: public repository, MIT license.
- 1python -m pytest --collect-only -q -o addopts= (depreciation-register-automation/)
- 2len(depreciation_engine.engine.REGISTRY)
- 3distinct rule-id prefixes in depreciation_engine.engine.REGISTRY
- 4samples/*.json after python run.py
- 5len(depreciation_engine.model.DOC_TYPES)
- 6depreciation_engine/tests boundary tests on the cent, the month and the threshold
- 7python run.py (depreciation-register-automation/); depreciation_engine/tests planted-defect suite
- 8depreciation_engine/generate.py DEFECTS fully_depreciated_still_carrying / depreciation_before_service
- 9depreciation_engine/generate.py DEFECTS rollforward_untied_to_register -> roll_closing_ties_register
- 10python -m pytest --collect-only -q -o addopts= (depreciation-register-automation/)
- 11len(depreciation_engine.engine.REGISTRY)
- 12len(depreciation_engine.generate.DEFECTS)
- 13depreciation_engine/tests: every registered rule has a planted defect
- 14depreciation-register-automation/README.md and depreciation_engine read-only / determinism tests
- 15depreciation_engine/model.py DOC_TYPES
- 16depreciation_engine/engine.py and model.py scope notes
- 17depreciation_engine/engine.py life_ and cap_ families
- 18depreciation_engine/generate.py
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