Originally created by: grynn-in
Decision record. Prompted by [#155] and by the decision to discard demo data and load only through the F8 upload path.
The convention, as the working code implements it:
Signed at the boundary, split once in silver, never inferred again.
- Every source adapter emits a signed
amount: positive = debit, negative = credit. Whatever the source encodes it as — magnitude plus a flag, two columns, a natural sign — is resolved in the adapter, once.silver_gl_entriessplits that signed amount into non-negativedebit_amount/credit_amount.- Everything downstream uses
period_debit - period_credit, which isperiod_net_amount. Signed. Neverabs(), never re-derived from anything else.- The invariant that makes it checkable: every voucher nets to zero when read signed.
Nothing needs inventing. What needs doing is stating it once and stopping the three statements from disagreeing again.
| Place | Says | State |
|---|---|---|
models/silver/silver_gl_entries.sql:38-47 |
"positive = debit, negative = credit … the raw signed amount provably balances (every voucher nets to 0), so the sign is the source of truth" | Correct, but its premise is false for D365. The demo generator writes magnitude + IsCredit, so nothing arrives negative. |
models/staging/d365_fo/stg_d365_fo__gl_entries.sql:38 |
entries.AccountingCurrencyAmount as amount |
Broken (#155). Drops IsCredit, so every credit enters as a debit. PR [#157] fixes it. |
models/gold/gold_consolidated_trial_balance.sql:43-45 |
"NOT period_net_amount, which is a positive magnitude (see [#64])" |
Stale. konsol's patch update_period_net_measure_expression.py redefined period_net_amount as sum(debit_amount) - sum(credit_amount) — which is exactly period_debit - period_credit. The two are now identical. The comment warns against a hazard that no longer exists and tells the next reader the measure means something it does not. |
That third one is the same failure mode as the konsol.currency_sync comment fixed in [#150]: a comment describing a design that was superseded, left in place to mislead the next person.
silver_gl_entries.sql, the Trial Balance Submission branch:
{# a TB row carries explicit debit and credit columns — no sign derivation #}
tbs.credit_amount as credit_amount,
tbs.debit_amount as debit_amount,
A TB CSV states debit and credit explicitly, so there is no sign to infer and #155's bug class is impossible on that path by construction. That is the strongest argument for the upload-only decision, and it is worth saying out loud rather than treating as a side effect.
clickhouse/demo-data.sql and replaced it with raw-schema.sql — DDL only, no rows. Its credits were unsigned, i.e. it was a carrier of [#155].scripts/generate_demo_data.py still exists but nothing loads its output.So the remaining question is not whether, it is what the upload format must carry — and that is the part with a hole in it.
Trial Balance Submission hardcodes every dimension to ''. If the entire demo loads through it:
| Capability | State under upload-only |
|---|---|
| Cost centre / department / business unit | Empty on every row, every entity |
| Allocation engine | No input — it groups gold_trial_balance by cost centre |
| Dimension harmonization (konsol#111) | No demo case exists |
| Drill-down | One synthetic line per account per period; transaction count always 1 |
| IC elimination (#148) | Stays at 0 rows — a TB row carries no counterparty |
konsol#113 is therefore a prerequisite, not a nice-to-have. Its TB_MGMT file (P&L only, division mandatory) and partner_data_area_id on IC accounts are exactly the two columns that close this. Shipping upload-only demo data before [#113] leaves the product unable to demonstrate allocations, harmonization, drill-down or eliminations.
gold_consolidated_trial_balance.sql:43-45. period_net_amount and period_debit - period_credit are the same thing now; say so, and drop the [#64] warning.models/staging/README or the _staging__sources.yml header), and link both the silver split and the per-source balance test to it.scripts/generate_demo_data.py once the replacement model lands. A generator nothing loads is a third statement of the convention waiting to rot — it is the one that wrote magnitude-plus-flag in the first place.Related: [#155], [#157], [#156], konsol#113, konsol#127, konsol#121.
Tickets: #113
Tickets: #150
Tickets: #155
Tickets: #156
Tickets: #157
Tickets: #166
Tickets: #168
Tickets: #173
Tickets: #174
Tickets: #175
Tickets: #64
Originally posted by: grynn-in
Rescoped on 12 Sep 2026 after an issue review against main.
Step 1 is done in [#157]:
tests/assert_d365_gl_vouchers_balance.sqlexists, andstg_d365_fo__gl_entriesstates that the amount arrives signed. Step 4 is overtaken by the demo removal.Remaining:
gold_consolidated_trial_balance.sql.scripts/generate_demo_data.py. Its HER inserts were the last trace of grynn-in/konsol#82.Related
Tickets:
#157Tickets:
#64Ticket changed by: grynn-in